当前位置: 首页 > news >正文

温州网站建设托管医疗企业网站模板

温州网站建设托管,医疗企业网站模板,wordpress高级图片主题,久久建筑王设计原则 一、单一原则 1. 如何理解单一职责原则 单一职责原则#xff08;Single Responsibility Principle#xff0c;简称SRP#xff09;#xff0c;它要求一个类或模块应该只负责一个特定的功能。实现代码的高内聚和低耦合#xff0c;提高代码的可读性和可维护性。 …设计原则 一、单一原则 1. 如何理解单一职责原则 单一职责原则Single Responsibility Principle简称SRP它要求一个类或模块应该只负责一个特定的功能。实现代码的高内聚和低耦合提高代码的可读性和可维护性。 我们可以把模块看作比类更加抽象的概念类也可以看作模块。或者把模块看作比类更加粗粒度的代码块模块中包含多个类多个类组成一个模块。 单一职责原则的定义就是一个类只负责完成一个职责或者功能。不要设计大而全的类要设计粒度小、功能单一的类。 如果一个类包含了两个或者两个以上业务不相干的功能那它职责不够单 一应该将它拆分成多个功能更加单一、粒度更细的类。 简单的例子 class Employee {private String name;private String position;private double baseSalary;public Employee(String name, String position, double baseSalary) {this.name name;this.position position;this.baseSalary baseSalary; }//... Getter 和 Setter 方法public double calculateSalary() {// 计算员工工资的逻辑return baseSalary * 1.2;}public void saveEmployee() {// 保存员工信息到数据库的逻辑} }上面的代码中 Employee 类负责了员工信息的管理、工资计算以及员工信息的持 久化。这违反了单一职责原则。为了遵循该原则我们可以将这些功能拆分到不同的类中 class Employee {private String name;private String position;private double baseSalary;public Employee(String name, String position, double baseSalary) {this.name namethis.position position;this.baseSalary baseSalary;}//... Getter 和 Setter 方法public double calculateSalary() {// 计算员工工资的逻辑return baseSalary * 1.2;} }class EmployeeRepository {public void saveEmployee(Employee employee) {// 保存员工信息到数据库的逻辑} }在遵循单一职责原则的代码中我们将员工信息的持久化操作从 Employee 类中抽 离出来放到了一个新的 EmployeeRepository 类中。 Employee 类只负责员工信息的管理和工资计算而 EmployeeRepository 类负责员工信息的持久 化操作。这样每个类都只关注一个特定的职责更易于理解、维护和扩展。 遵循单一职责原则有助于提高代码的可读性、可维护性和可扩展性。但这个原则并不是绝对的需要根据具体情况来判断是否需要拆分类和模块。过度拆分可能导致过多的类和模块反而增加系统的复杂度。 2. 如何判断类的职责是否足够单一 对于一个类是否职责单一的判定是很难拿捏的。我举一个更加贴近实际 的例子来给你解释一下。 在一个社交产品中我们用下面的 UserInfo 类来记录用户的信息。 UserInfo 类的设计是否满足单一职责原则呢 public class UserInfo {private long userId;private String username;private String email;private String telephone;private String avatarUrl;private String province; // 省private String cityOf; // 市private String region; // 区private String detailedAddress; // 详细地址// ... 省略其他属性和方法... } 对于这个问题有两种不同的观点。 观点1UserInfo 类包含的都是跟用户相关的信息所有的属性和方法都隶属于用户这样一个业务模型满足单一职责原则 另一观点2地址信息在 UserInfo 类中所占的比重比较高可以继续拆分成独立的 Address 类UserInfo 只保留除 Address 之外的其他信息拆分之后的两个类的职责更加单一。 哪种观点更对呢 我们不能脱离具体的应用场景。 如果在这个社交产品中用户的地址信息跟其他信息一样只是单纯地用来展示那 UserInfo 现在的设计就是合理的。 如果这个社交产品发展得比较好之后又在产品中添加了电商的模块用户的地址信息还会用在电商物流中那我们最好将地址信息从 UserInfo 中拆分出来独立成用户物流信息或者叫地址信息、收货信息 等。 如果做这个社交产品的公司发展得越来越好公司内部又开发出了其他产品可以理解为其他 App。公司希望支持统一账号系统也就是用户一个账号可以在公司内部的所有产品中登录。这个时候我们就需要继续对 UserInfo 进行拆分将跟身份认证相关的信息比如email、telephone 等抽取成独立的类。 综上所述一个类的职责是否足够单一我们并没有一个非常明确的、可以量化的标准。 所以我们可以先写一个粗粒度的类来满足业务需求。随着业务的发展如果粗粒度的类越来越庞大代码越来越多且难以维护的时候我们就可以将这个粗粒度的类拆分成几个更细粒度的类也就是持续重构 。 下面这几条经验方便我们去思考类的设计是否职责单一 类中的代码行数、函数或属性过多会影响代码的可读性和可维护性我们就需 要考虑对类进行拆分类依赖的其他类过多或者依赖类的其他类过多不符合高内聚、低耦合的设计思想我们就需要考虑对类进行拆分私有方法过多我们就要考虑能否将私有方法独立到新的类中设置为 public 方法供更多的类使用从而提高代码的复用性比较难给类起一个合适名字很难用一个业务名词概括或者只能用一些笼统的 Manager、Context 之类的词语来命名这就说明类的职责定义得可能不够清晰类中大量的方法都是集中操作类中的某几个属性比如在 UserInfo 例子中 如果一半的方法都是在操作 address 信息那就可以考虑将这几个属性和对应的方法拆分出来。 3. 类的职责是否设计得越单一越好 为了满足单一职责原则是不是把类拆得越细就越好呢答案是否定的。我们还是通 过一个例子来解释一下。Serialization 类实现了一个简单协议的序列化和反序列功 能具体代码如下 /** * Protocol format: identifier-string;{gson string} * For example: UEUEUE;{a:A,b:B} */ public class Serialization {private static final String IDENTIFIER_STRING UEUEUE;;private Gson gson;public Serialization() {this.gson new Gson();}public String serialize(MapString, String object) {StringBuilder textBuilder new StringBuilder();textBuilder.append(IDENTIFIER_STRING);textBuilder.append(gson.toJson(object));return textBuilder.toString();}public MapString, String deserialize(String text) {if (!text.startsWith(IDENTIFIER_STRING)) {return Collections.emptyMap();}String gsonStr text.substring(IDENTIFIER_STRING.length());return gson.fromJson(gsonStr, Map.class);} }如果我们想让类的职责更加单一我们对 Serialization 类进一步拆分拆分成一个 只负责序列化工作的 Serializer 类和另一个只负责反序列化工作的 Deserializer 类。 拆分后的具体代码如下所示 public class Serializer {private static final String IDENTIFIER_STRING UEUEUE;;private Gson gson;public Serializer() {this.gson new Gson();}public String serialize(MapString, String object) {StringBuilder textBuilder new StringBuilder();textBuilder.append(IDENTIFIER_STRING);textBuilder.append(gson.toJson(object));return textBuilder.toString();} }public class Deserializer {private static final String IDENTIFIER_STRING UEUEUE;;private Gson gson;public Deserializer() {this.gson new Gson();}public MapString, String deserialize(String text) {if (!text.startsWith(IDENTIFIER_STRING)) {return Collections.emptyMap();}String gsonStr text.substring(IDENTIFIER_STRING.length());return gson.fromJson(gsonStr, Map.class);} }虽然经过拆分之后Serializer 类和 Deserializer 类的职责更加单一了也带来了新的问题。如果我们修改了协议的格式数据标识从“UEUEUE”改为“DFDFDF” 或序列化方式从 JSON 改为了XML那 Serializer 类和 Deserializer 类都需要做相 应的修改代码的内聚性显然没有原来 Serialization 高了。如果我们仅仅对 Serializer 类做了协议修改忘记修改 Deserializer 类的代码那就会导致序列化、反序列化不匹配程序运行出错拆分之后代码的可维护性变差了。 不管是应用设计原则还是设计模式最终的目的还是提高代码的可读性、可扩展性、复用性、可维护性等。我们在考虑应用某一个设计原则是否合理的时候也可以以此作为最终的考量标准。 二、开闭原则 1. 原理概述 开闭原则 Open Closed Principle 简称OCP 它的英文描述是 software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。我们把它翻译成中文就是软件实体模 块、类、方法等应该“对扩展开放、对修改关闭”。 以下是一个常见的生产环境中的例子我们将展示一个简化的电商平台的订单折扣策 略。 class Order {private double totalAmount;public Order(double totalAmount) {this.totalAmount totalAmount;}// 计算折扣后的金额public double getDiscountedAmount(String discountType) {double discountedAmount totalAmount;if (discountType.equals(FESTIVAL)) {// 节日折扣9折discountedAmount totalAmount * 0.9; } else if (discountType.equals(SEASONAL)) {// 季节折扣8折discountedAmount totalAmount * 0.8; }return discountedAmount;} }上述代码中 Order 类包含一个计算折扣金额的方法它根据不同的折扣类型计算折扣。当我们需要添加新的折扣类型时就不得不修改getDiscountedAmount 方法的代码这显然是不合理的违反了开闭原则。 修改为遵循开闭原则的代码 // 抽象折扣策略接口 interface DiscountStrategy {double getDiscountedAmount(double totalAmount); } // 节日折扣策略 class FestivalDiscountStrategy implements DiscountStrategy {Overridepublic double getDiscountedAmount(double totalAmount) {return totalAmount * 0.9; // 9折} } // 季节折扣策略 class SeasonalDiscountStrategy implements DiscountStrategy {Overridepublic double getDiscountedAmount(double totalAmount) {return totalAmount * 0.8; // 8折} } class Order {private double totalAmount;private DiscountStrategy discountStrategy;public Order(double totalAmount, DiscountStrategy discountStrategy) {this.totalAmount totalAmount;this.discountStrategy discountStrategy;}public void setDiscountStrategy(DiscountStrategy discountStrategy) {this.discountStrategy discountStrategy;}// 计算折扣后的金额public double getDiscountedAmount() {return discountStrategy.getDiscountedAmount(totalAmount);} }在遵循开闭原则的代码中我们定义了一个抽象的折扣策略接口DiscountStrategy 然后为每种折扣类型创建了一个实现该接口的策略类。 Order类使用组合的方式包含一个 DiscountStrategy 类型的成员变量以便 在运行时设置或更改折扣策略可以通过编码配置、依赖注入等形式。当我们需要添加新的折扣类型时只需实现 DiscountStrategy 接口无需修改现有的 Order 代码。这个例子遵循了开闭原则。 2. 修改代码就意味着违背开闭原则吗 开闭原则的核心思想是尽量减少对现有代码的修改以降低修改带来的风险和影响。在实际开发过程中完全不修改代码是不现实的。当需求变更或者发现代码中的错误时修改代码是正常的。开闭原则鼓励我们通过设计更好的代码结构使得在添加新功能或者扩展系统时尽量减少对现有代码的修改。 以下是一个简化的日志记录器的示例展示了在适当情况下修改代码也不违背开闭 原则。在这个例子中我们的应用程序支持将日志输出到控制台和文件。假设我们需要添加一个新功能以便在输出日志时同时添加一个时间戳。 interface Logger {void log(String message); }class ConsoleLogger implements Logger {Overridepublic void log(String message) {System.out.println(Console: message);} }class FileLogger implements Logger {Overridepublic void log(String message) {System.out.println(File: message);// 将日志写入文件的实现省略} }为了添加时间戳功能我们需要修改现有的 ConsoleLogger 和 FileLogger 类。 虽然我们需要修改代码但由于这是对现有功能的改进而不是添加新的功能所以这种修改是可以接受的不违背开闭原则。 interface Logger {void log(String message); }class ConsoleLogger implements Logger {Overridepublic void log(String message) {String timestamp LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);System.out.println(Console [ timestamp ]: message);} }class FileLogger implements Logger {Overridepublic void log(String message) {String timestamp LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);String logMessage File [ timestamp ]: message;System.out.println(logMessage);// 将日志写入文件的实现省略} }3. 如何做到“对扩展开放、修改关闭” 为了尽量写出扩 展性好的代码我们要时刻具备扩展意识、抽象意识、封装意识。这些“潜意识”可能 比任何开发技巧都重要。 我们给自己的定位是“工程师”而非码农那我们在写任何一段代码时都应该思 考一些问题 事先留好扩展点以便在未来需求变更的时候不需要改动代码整体结构的情况下做到最小代码改动能够很灵活地插入到扩展点上做到“对扩展开放、对修改 关闭”。我们还要识别出代码可变部分和不可变部分要将可变部分封装起来隔离变化提供抽象化的不可变接口给上层系统使用。当具体的实现发生变化的时 候我们只需要基于相同的抽象接口扩展一个新的实现替换掉老的实现即可上游系统的代码几乎不需要修改。 三、里氏替换原则 1. 原理概述 里式替换原则的英文翻译是Liskov Substitution Principle缩写为 LSP 。 Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。使用基类引用指针的函数必须能够在不知情的情况下使用派生类的对象。 子类对象object of subtype/derived class能够替换程序program中父类对象object of base/parent class出现的任何地方并且保证原来程序的逻辑行为 behavior不变及正确性不被破坏。 有点儿多态的味道。 以下是一个简单的示例 // 基类鸟类 public class Bird {public void fly() {System.out.println(I can fly);} }// 子类企鹅类 public class Penguin extends Bird {// 企鹅不能飞所以覆盖了基类的fly方法但这违反了里氏替换原则public void fly() {throw new UnsupportedOperationException(Penguins cant fly);} }为了遵循里氏替换原则我们可以重新设计类结构将能飞的行为抽象到一个接口中让需要飞行能力的鸟类实现这个接口 // 飞行行为接口 public interface Flyable {void fly(); } // 基类鸟类 public class Bird { } // 子类能飞的鸟类 public class FlyingBird extends Bird implements Flyable {Overridepublic void fly() {System.out.println(I can fly);} } // 子类企鹅类不实现Flyable接口 public class Penguin extends Bird { } 通过这样的设计我们遵循了里氏替换原则同时也保证了代码的可维护性和复用性。 再来看一个基于数据库操作的案例。假设我们正在开发一个支持多种数据库的程序包括MySQL、PostgreSQL和SQLite。可以使用里氏替换原则来设计合适的类结构确保代码的可维护性和扩展性。 首先定义一个抽象的 Database 基类它包含一些通用的数据库操作方法 如 connect() 、 disconnect() 和 executeQuery() 。这些方法的具体实现将在子类中完成。 public abstract class Database {public abstract void connect();public abstract void disconnect();public abstract void executeQuery(String query); }然后为每种数据库类型创建一个子类继承自 Database 基类。这些子类需要实现基类中定义的抽象方法并可以添加特定于各自数据库的方法。 public class MySQLDatabase extends Database {Overridepublic void connect() {// 实现MySQL的连接逻辑}Overridepublic void disconnect() {// 实现MySQL的断开连接逻辑}Overridepublic void executeQuery(String query) {// 实现MySQL的查询逻辑}// 其他针对MySQL的特定方法 } public class PostgreSQLDatabase extends Database {// 类似地为PostgreSQL实现相应的方法 } public class SQLiteDatabase extends Database {// 类似地为SQLite实现相应的方法 }这样设计的好处是我们可以在不同的数据库类型之间灵活切换而不需要修改大量代码。只要这些子类遵循里氏替换原则我们就可以放心地使用基类的引用来操作不 同类型的数据库。例如 public class DatabaseClient {private Database database;public DatabaseClient(Database database) {this.database database;}public void performDatabaseOperations() {database.connect();database.executeQuery(SELECT * FROM users);database.disconnect();} } public class Main {public static void main(String[] args) {// 使用MySQL数据库DatabaseClient client1 new DatabaseClient(new MySQLDatabase());client1.performDatabaseOperations();// 切换到PostgreSQL数据库DatabaseClient client2 new DatabaseClient(new PostgreSQLDatabase());client2.performDatabaseOperations();// 切换到SQLite数据库DatabaseClient client3 new DatabaseClient(new SQLiteDatabase());client3.performDatabaseOperations();} }通过遵循里氏替换原则我们确保了代码的可维护性和扩展性。如果需要支持新的数据库类型只需创建一个新的子类实现 Database 基类中定义的抽象方法即可。 虽然从定义描述和代码实现上来看多态和里式替换有点类似但它们关注的角度是不一样的。 多态是面向对象编程的一大特性也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种设计原则是用来指导继承关系中子类该如何设计的子类的设计要保证在替换父类的时候不改变原有程序的逻辑以及不破坏原有程序的正确性。 2. 哪些代码违背了里氏替换原则 违背里氏替换原则LSP的代码通常具有以下特征 2.1 子类覆盖或修改了基类的方法 当子类覆盖或修改基类的方法时可能导致子类无法替换基类的实例而不引起问题。这违反了LSP会导致代码变得脆弱和不易维护。 比如在下面代码中Penguin 类覆盖了Bird 类的fly() 方法抛出了一个异常。这违反了LSP因为现在Penguin实例无法替换Bird 实例。 public class Bird {public void fly() {System.out.println(I can fly);} } public class Penguin extends Bird {Overridepublic void fly() {throw new UnsupportedOperationException(Penguins cant fly);} }2.2 子类违反了基类的约束条件 当子类违反了基类中定义的约束条件如输入、输出或异常等也会违反LSP。 在下面这个例子中NonNegativeStack 类违反了Stack基类的约束条件因为它在 push() 方法中添加了一个新的约束即只允许非负数入栈。这使得 NonNegativeStack 实例无法替换 Stack 实例违反了LSP。 public class Stack {private int top;private int[] elements;public Stack(int size) {elements new int[size];top -1;}public void push(int value) {if (top elements.length - 1) {throw new IllegalStateException(Stack is full);}elements[top] value;}public int pop() {if (top 0) {throw new IllegalStateException(Stack is empty);}return elements[top--];} } // 正数的栈 public class NonNegativeStack extends Stack {public NonNegativeStack(int size) {super(size);}Overridepublic void push(int value) {// 只允许非负数入栈if (value 0) {throw new IllegalArgumentException(Only non-negative values are allowed);}super.push(value);} } // 正确的写法 public class NonNegativeStack extends Stack {public NonNegativeStack(int size) {super(size);}// 重新定义新的方法实现新的约束public void pushNonNegative(int value) {if (value 0) {throw new IllegalArgumentException(Only non-negative values are allowed);}super.push(value);} }2.3 子类与基类之间非is-a关系 当子类与基类之间缺乏真正的is-a关系时也可能导致违反LSP。例如如果一个类继承自另一个类仅仅因为它们具有部分相似性而不是完全的is-a关系那么这种继承关系可能不满足LSP。 为了避免违反LSP我们需要在设计和实现过程中注意以下几点 确保子类和基类之间存在真正的is-a关系。遵循其他设计原则如单一职责原则、开闭原则和依赖倒置原则。 四、接口隔离原则 1. 原理概述 接口隔离原则的英文翻译是“ Interface Segregation Principle”缩写为 ISP。 “Clients should not be forced to depend upon interfaces that they do not use。”直译成中文的话就是客户端不应该强迫依赖它不需要的接口。其中的“客户端”可以理解为接口的调用者或者使用者。 它要求我们将大且臃肿的接口拆分成更小、更专注的接口以确保类之间的解耦。这样客户端只需要依赖它实际使用的接口而不需要依赖那些无关的接口。 在软件开发中既可以把接口看作一组抽象的约定也可以具体指系统与系统之间的 API 接口还可以特指面向对象编程语言中的接口等。 可以把“接口”理解为下面三种东西 一组 API 接口集合单个 API 接口或函数OOP 中的接口概念 接口隔离原则有以下几个要点 将一个大的、通用的接口拆分成多个专用的接口。这样可以降低类之间的耦合度提高代码的可维护性和可读性。为每个接口定义一个独立的职责。这样可以确保接口的粒度适当同时也有助于遵循单一职责原则。在定义接口时要考虑到客户端的实际需求。客户端不应该被迫实现无关的接口方法。 2. 示例 开发一个机器人程序机器人具有多种功能如行走、飞行和工作。可以为这些功能创建一个统一的接口 public interface Robot {void walk();void fly();void work(); }然而这个接口并不符合接口隔离原则因为它将多个功能聚合在了一个接口中。对于那些只需要实现部分功能的客户端来说这个接口会导致不必要的依赖。为了遵循接口隔离原则我们应该将这个接口拆分成多个更小、更专注的接口 public interface Walkable {void walk(); } public interface Flyable {void fly(); } public interface Workable {void work(); }现在我们可以根据需要为不同类型的机器人实现不同的接口。例如对于一个只能行走和工作的机器人我们只需要实现 Walkable 和 Workable 接口 public class WalkingWorkerRobot implements Walkable, Workable {Overridepublic void walk() {// 实现行走功能}Overridepublic void work() {// 实现工作功能} }一个接口只定义一个方法确实可以满足接口隔离原则但这并不是一个绝对的标准。 在设计接口时我们需要权衡接口的粒度和实际需求。过度拆分接口可能导致过多的单方法接口这会增加代码的复杂性降低可读性和可维护性。 关键在于确保接口的职责清晰且单一以便客户端只需依赖它们真正需要的接口。在某些情况下一个接口包含多个方法是合理的只要这些方法服务于一个单一的职责。例如一个数据库操作接口可能包含 connect() 、 disconnect() 、 executeQuery() 等方法这些方法都是数据库操作的一部分因此可以放在同一个接口中。 五、依赖倒置原则 1. 原理 依赖倒置原则Dependency Inversion Principle简称 DIP是面向对象设计的五大原则SOLID之一。这个原则强调要依赖于抽象而不是具体实现。遵循这个原则可以使系统的设计更加灵活、可扩展和可维护。 依赖倒置原则有两个关键点 高层模块不应该依赖于低层模块它们都应该依赖于抽象。抽象不应该依赖于具体实现具体实现应该依赖于抽象。 倒置Inversion在这里的确是指“反过来”的意思。在依赖倒置原则Dependency Inversion Principle, DIP中我们需要改变依赖关系的方向使得高层模块和低层模块都依赖于抽象而不是高层模块直接依赖于低层模块。这样一来依赖关系就从直接依赖具体实现“反过来”依赖抽象了。 在没有应用依赖倒置原则的传统软件设计中高层模块通常直接依赖于低层模块。这 会导致系统的耦合度较高低层模块的变化很容易影响到高层模块。当我们应用依赖倒置原则时高层模块和低层模块的依赖关系发生了改变它们都依赖于抽象例如接口或抽象类而不再是高层模块直接依赖于低层模块。这样我们就实现了依赖 关系的“倒置”。 这种“倒置”的依赖关系使得系统的耦合度降低提高了系统的可维护性和可扩展性。 因为当低层模块的具体实现发生变化时只要不改变抽象高层模块就不需要进行调整。所以这个原则叫做依赖倒置原则。 2. 如何理解抽象 当我们在讨论依赖倒置原则中的抽象时绝对不能仅仅把他理解为一个接口。抽象的目的是将关注点从具体实现转移到概念和行为使得我们在设计和编写代码时能够更加关注问题的本质。通过使用抽象我们可以创建更加灵活、可扩展和可维护的系统。 事实上抽象是一个很广泛的概念它可以包括接口、抽象类以及由大量接口抽象类和实现组成的更高层次的模块。通过将系统分解为更小的可复用的组件我们可以实现更高层次的抽象。这些组件可以独立地进行替换和扩展从而使整个系统更加灵活。 在依赖倒置原则的背景下我们可以从以下几个方面理解抽象 2.1 接口 接口是 Java 中实现抽象的一种常见方式。接口定义了一组方法签名表示实现该接口的类应具备哪些行为。接口本身并不包含具体实现所以它强调了行为的抽象。 假设我们正在开发一个在线购物系统其中有一个订单处理模块。订单处理模块需要与不同的支付服务提供商如 PayPal、Stripe 等进行交互。如果我们直接依赖于支付服务提供商的具体实现那么在更换支付服务提供商或添加新的支付服务提供商时我们可能需要对订单处理模块进行大量修改。为了避免这种情况我们应该依赖于接口而不是具体实现。 首先我们定义一个支付服务接口 public interface PaymentService {boolean processPayment(Order order); }然后为每个支付服务提供商实现该接口 public class PayPalPaymentService implements PaymentService {Overridepublic boolean processPayment(Order order) {// 实现 PayPal 支付逻辑} } public class StripePaymentService implements PaymentService {Overridepublic boolean processPayment(Order order) {// 实现 Stripe 支付逻辑} }现在我们可以在订单处理模块中依赖 PaymentService 接口而不是具体的实现 public class OrderProcessor {private PaymentService paymentService;public OrderProcessor(PaymentService paymentService) {this.paymentService paymentService;}public void processOrder(Order order) {// 其他订单处理逻辑...boolean paymentResult paymentService.processPayment(order);// 根据 paymentResult 处理支付结果} }当我们需要更换支付服务提供商或添加新的支付服务提供商时只需要提供一个新的实现类而不需要修改 OrderProcessor 类。我们可以通过构造函数注入不同的支付服务实现使得系统更加灵活和可扩展。 2.2 抽象类 抽象类是另一种实现抽象的方式。与接口类似抽象类也可以定义抽象方法表示子类应该具备哪些行为。不过抽象类还可以包含部分具体实现这使得它们比接口更加灵活。 abstract class Shape {abstract double getArea();void displayArea() {System.out.println(面积为: getArea());} } class Circle extends Shape {private final double radius;Circle(double radius) {this.radius radius;}Overridedouble getArea() {return Math.PI * Math.pow(radius, 2);} }class Square extends Shape {private final double side;Square(double side) {this.side side;}Overridedouble getArea() {return Math.pow(side, 2);} }在这个示例中我们定义了一个抽象类 Shape 它具有一个抽象方法 getArea 用于计算形状的面积。同时它还包含了一个具体方法 displayArea 用于打印面 积。 Circle 和 Square 类继承了 Shape 分别实现了 getArea 方法。在其他类中我们可以依赖抽象Shape而非Square和Circle。 2.3 高层模块 在某些情况下我们可以通过将系统分解为更小的、可复用的组件来实现抽象。这些组件可以独立地进行替换和扩展从而使整个系统更加灵活。这种抽象方法往往在软件架构和模块化设计中有所体现。 3. 如何理解高层模块和低层模块 在调用链上调用者属于高层被调用者属于低层。在平时的业务代码开发中高层模块依赖低层模块是没有任何问题的。这条原则主要用来指导框架层面的设计跟前面讲到的控制反转类似。 从业务代码上讲举一个简单的例子就是controller要依赖service的接口而不是实现service实现要依赖dao层的接口而不是实现调用者要依赖被调用者的接口而不是实现。 以一个简单的音频播放器为例高层模块 AudioPlayer 负责播放音频而音频文件的解码由低层模块 Decoder 实现。为了遵循依赖倒置原则我们可以引入一个抽 象的解码器接口 interface AudioDecoder {AudioData decode(String filePath); } class AudioPlayer {private final AudioDecoder decoder;public AudioPlayer(AudioDecoder decoder) {this.decoder decoder;}public void play(String filePath) {AudioData audioData decoder.decode(filePath);// 使用解码后的音频数据进行播放} } class MP3Decoder implements AudioDecoder {Overridepublic AudioData decode(String filePath) {// 实现 MP3 文件解码} }在这个例子中我们将高层模块 AudioPlayer 和低层模块 MP3Decoder 解耦使它们都依赖于抽象接口 AudioDecoder 。这样可以根据需要轻松地更换音频解码器例如支持不同的音频格式而不影响音频播放器的逻辑。为了支持新的音频格式我们只需要实现新的解码器类并将其传递给 AudioPlayer 。 假设我们现在要支持 WAV 格式的音频文件我们可以创建一个实现 AudioDecoder 接口的新类 class WAVDecoder implements AudioDecoder {Overridepublic AudioData decode(String filePath) {// 实现 WAV 文件解码 }然后在创建 AudioPlayer 对象时我们可以根据需要选择使用 MP3Decoder 或 WAVDecoder : public static void main(String[] args) {AudioDecoder mp3Decoder new MP3Decoder();AudioPlayer mp3Player new AudioPlayer(mp3Decoder);mp3Player.play(example.mp3);AudioDecoder wavDecoder new WAVDecoder();AudioPlayer wavPlayer new AudioPlayer(wavDecoder);wavPlayer.play(example.wav); }我们拿Tomcat 这个 Servlet 容器作为例子来解释一下。 Tomcat 是运行 Java Web 应用程序的容器。我们编写的 Web 应用程序只需要部署在 Tomcat 容器下便可以被 Tomcat 容器调用执行。按照之前的划分原则Tomcat 就是高层模块我们编写的 Web 应用程序就是低层模块。Tomcat 和 应用程序之间并没有直接的依赖关系两者都依赖同一个“抽象”也就是 Servlet 规范。Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节而 Tomcat 容器和应用程序依赖 Servlet 规范。这样做的好处就是tomcat中可以运行任何实现了servlet规范的应用程序同时我们编写的servlet实现web工程也可以 运行在不同的web服务器中。 4. IOC容器 控制反转是一种软件设计原则它将传统的控制流程颠倒过来将控制权交给一个中 心化的容器或框架。 依赖注入是指不通过 new 的方式在类内部创建依赖类对象而是将依赖的类对象在外部创建好之后通过构造函数、函数参数等方式传递或注入给类使用。 结合控制翻转和依赖注入我们只要保证依赖抽象而不是实现就能很轻松的替换实现。如给容器注入一个myslq的数据则所有依赖数据源的部分会自动使用 mysql如果想替换数据源则仅仅需要给容器注入一个新的数据源就好了不需要修改一行代码。 六、迪米特原则 1. 原理 迪米特法则Law of Demeter, LoD , 核心思想是一个对象应该 尽量少地了解其他对象降低对象之间的耦合度从而提高代码的可维护性和可扩展性。 主要指导原则如下 类和类之间尽量不直接依赖。有依赖关系的类之间尽量只依赖必要的接口。 2. 类之间不直接依赖 不该有直接依赖关系的类之间不要有依赖这个原则强调的是降低类与类之间的耦合度避免不必要的依赖。这意味着我们应该使用抽象如接口或抽象类来解耦具体实现。举例理解 假设我们有一个简单的报告生成系统它需要从不同类型的数据源如数据库、文件、API等获取数据并输出不同格式的报告如CSV、JSON、XML等。 // 具体的数据库类 class Database {public String fetchData() {// 从数据库中获取数据return data from database;} } // 具体的报告生成类 class ReportGenerator {private Database database;public ReportGenerator(Database database) {this.database database;}public String generateCSVReport() {String data database.fetchData();// 将数据转换为CSV格式return CSV report: data;} }在上述实现中 ReportGenerator 类直接依赖于具体的 Database 类。这意味着如果我们想从其他类型的数据源如文件获取数据或者使用不同的数据库实现 需要修改 ReportGenerator 类。这违反了开闭原则对扩展开放对修改封闭并增加了类与类之间的耦合。 为了遵循 不该有直接依赖关系的类之间不要有依赖原则我们可以引入抽象来解耦具体实现。下面是一个修改后的实现 // 数据源接口 interface DataSource {String fetchData(); } // 具体的数据库类 class Database implements DataSource {Overridepublic String fetchData() {// 从数据库中获取数据return data from database;} } // 具体的文件类 class FileDataSource implements DataSource {Overridepublic String fetchData() {// 从文件中获取数据return data from file;} } // 报告生成类 class ReportGenerator {private DataSource dataSource;public ReportGenerator(DataSource dataSource) {this.dataSource dataSource;}public String generateCSVReport() {String data dataSource.fetchData();// 将数据转换为CSV格式return CSV report: data;} } 在修改后的实现中我们引入了 DataSource 接口并使 ReportGenerator 类依赖于该接口而不是具体的实现。这样我们可以轻松地为报告生成器添加新的数据源类型而无需修改现有代码。 3. 只依赖必要的接口 有依赖关系的类之间尽量只依赖必要的接口这个原则强调的是当一个类需要依赖另一个类时应该尽可能地依赖于最小化的接口。 以用户信息管理案例给大家讲解 public interface UserService {boolean register(String cellphone, String password);boolean login(String cellphone, String password);UserInfo getUserInfoById(long id);UserInfo getUserInfoByCellphone(String cellphone); } public interface RestrictedUserService {boolean deleteUserByCellphone(String cellphone);boolean deleteUserById(long id); } public class UserServiceImpl implements UserService, RestrictedUserService {// ... 省略实现代码... }对于绝大部分场景我们可能只关心和删除无关的方法如UserController所以他只需要依赖他所需要的接口UserService即可: public class UserController{UserService userService;// ... 省略实现代码... }然而用户管理员需要更多的权限我们则可以通过组合的形式来实现让其依赖两个必要的接口 public class UserManagerController{UserService userService;RestrictedUserService restrictedUserService;// ... 省略实现代码... }再举一个例子假如我们要开一个飞行比赛我们可以写出如下的案例来满足迪米特法则 // 飞行行为接口 public interface Flyable {void fly(); } // 基类鸟类 public class Bird { } // 子类能飞的鸟类 public class Sparrow extends Bird implements Flyable {Overridepublic void fly() {System.out.println(sparrow can fly);} } // 子类飞机 public class Plane implements Flyable {Overridepublic void fly() {System.out.println(plane can fly);} } // 子类企鹅类不实现Flyable接口 public class Penguin extends Bird { } // public class AirRace {ListFlyable list;public void addFlyable(Flyable flyable){list.add(flyable);}// ... } 4. 灵活应用 在实际工作中确实需要在不同的设计原则之间进行权衡。迪米特法则Law of DemeterLoD是一种有助于降低类之间耦合度的原则但过度地应用迪米特法则可能导致代码变得复杂和难以维护。因此在实际项目中我们应该根据具体的场景和需求灵活地应用迪米特法则。以下是一些建议 避免过度封装尽管迪米特法则强调类之间的低耦合但是过度封装可能导致系 统变得难以理解和维护。当一个类需要访问另一个类的属性或方法时我们应该权衡封装的成本和收益而不是盲目地遵循迪米特法则。拒绝过度解耦在实际项目中过度解耦可能导致大量的中间层和传递性调用。当一个类需要访问另一个类的方法时如果引入大量的中间层会导致系统变得复杂和低效那么我们应该考虑放宽迪米特法则的约束。与其他设计原则和模式相结合在实际项目中我们应该灵活地将迪米特法则与 其他设计原则如单一职责原则、开闭原则等和设计模式如外观模式、代理模式等相结合。这样可以使我们在降低耦合度的同时保持代码的可读性、可维护性和可扩展性。考虑实际需求和场景在应用迪米特法则时我们应该关注实际的需求和场景。如果一个项目的需求和场景较为简单那么过度地应用迪米特法则可能导致不必 要的开发成本。相反如果一个项目的需求和场景较为复杂那么遵循迪米特法则可能有助于提高系统的稳定性和可维护性。
http://www.w-s-a.com/news/982090/

相关文章:

  • 淅川网站建设如何在百度上做自己的网站
  • 网站制作 南通有学给宝宝做衣服的网站吗
  • 做西式快餐店网站网络营销的含义是什么
  • 网络销售代理加盟南京seo排名扣费
  • 赤峰中国建设招标网站网站开发投标文件
  • 域名抢住网站婚庆网页设计
  • 公司网站建设的通知南宁怎么做网站
  • 搜狐快站建站教程电子商务网站后台模板
  • .gs域名做网站怎么样做网站有没有用
  • 肇庆住房和城乡建设局网站广州seo公司排名
  • j2ee网站开发买什么书网络媒体有哪些
  • 江西省住房建设部官方网站用多说的网站
  • 云课堂哪个网站做的好网站 集约化平台建设方案的通知
  • 撰写网站栏目规划怎么建自己的平台
  • 中国建设银行巴黎分行网站建设银行忘记密码网站首页
  • 网站左侧树形导航怎么做像wordpress一样的网站吗
  • 做网站用的书公司做网站 需要解决哪些问题
  • 电器网站建设策划书深圳动画制作
  • cpa网站建设wordpress支付宝微信收费吗
  • 权威网站排名桂林生活网论坛
  • 网站设计息济南网站建设济南
  • 安蓉建设总公司网站网站怎么做才能被百度收录
  • 电子商务网站业务流程分析做效果图的外包网站
  • wordpress仿站视频教程wordpress用什么php版本好
  • 郑州做网站九零后网络沧州做网站的专业公司
  • 小游戏网站建设可以自己做图片的软件
  • 湖南地税局官网站水利建设基金app仿制
  • 苏州网站设计kgwl建设网站需要用到哪些技术人员
  • 万户网络做网站如何亚马逊网站建设
  • 门户网站制作费用暴雪公司最新消息