Java 中常用的 23 种设计模式详解2
设计模式是在软件设计中反复出现的问题的通用解决方案。它们是经过多次验证和应用的指导原则,旨在帮助软件开发人员解决特定类型的问题,提高代码的可维护性、可扩展性和重用性。
设计模式是一种抽象化的思维方式,可以帮助开发人员更好地组织和设计他们的代码。它们提供了一种通用的框架,可以用于解决各种不同的软件设计问题。设计模式不是完整的代码,而是一种描述问题和解决方案之间关系的模板。
设计模式并不是一成不变的法则,而是根据不同的问题和情境来决定是否使用以及如何使用。了解和应用设计模式可以帮助开发人员更好地组织代码,提高代码的可读性和可维护性,同时也有助于促进团队之间的合作和沟通。
设计模式的分类
创建型模式(Creational) :关注对象的实例化过程,包括了如何实例化对象、隐藏对象的创建细节等。常见的创建型模式有单例模式、工厂模式、抽象工厂模式等。
结构型模式(Structural) :关注对象之间的组合方式,以达到构建更大结构的目标。这些模式帮助你定义对象之间的关系,从而实现更大的结构。常见的结构型模式有适配器模式、装饰器模式、代理模式等。
行为型模式(Behavioral) :关注对象之间的通信方式,以及如何合作共同完成任务。这些模式涉及到对象之间的交互、责任分配等。常见的行为型模式有观察者模式、策略模式、命令模式等。
设计模式的基本要素
模式名称 :每个设计模式都有一个简洁的名称,用于描述问题、解决方案和效果。这个名称有助于在交流中快速指代模式。
问题 :描述了在什么情况下应该考虑使用特定的设计模式。问题部分阐述了该模式试图解决的具体设计难题。
解决方案 :解决方案部分提供了一个详细的设计指南,描述了如何组织类、对象以及它们之间的关系,以解决特定问题。这包括了每个角色的职责、协作方式等。
效果 :描述了模式应用的效果及使用模式应权衡的问题。
种设计模式概览
设计模式间的关系
设计模式代码示例仓库地址 23 种设计模式 JAVA 实现
设计模式详解 1. 工厂方法模式(Factory Method) 问题 :
在软件设计中,我们经常遇到需要创建不同类型对象的情况。但是,如果直接在代码中实例化对象,会使代码紧密耦合在一起,难以维护和扩展。此外,如果对象的创建方式需要变化,那么就需要在整个代码中进行大量的修改。工厂方法模式旨在解决这个问题。
解决方案 :
工厂方法模式提供了一个创建对象的接口,但是将具体的对象创建延迟到子类中。这样,客户端代码不需要知道要创建的具体对象的类,只需要通过工厂方法来创建对象。这使得客户端代码与具体对象的创建解耦,提高了代码的灵活性和可维护性。
在工厂方法模式中,通常会定义一个抽象工厂类,其中包含一个创建对象的抽象方法,而具体的对象创建则由具体的子类实现。这样,每个具体的子类都可以根据需要创建不同类型的对象,而客户端代码只需要通过抽象工厂类来调用工厂方法,而不需要关心具体的对象创建细节。
效果 :
工厂方法模式的优点包括:
松耦合 :客户端代码与具体对象的创建解耦,使得系统更具弹性和可维护性。
扩展性 :通过添加新的具体工厂和产品子类,可以很容易地扩展系统以支持新的对象类型。
封装性 :将对象的创建集中在工厂类中,封装了对象的创建细节,使得客户端代码更简洁。
然而,工厂方法模式也可能引入一些额外的复杂性,因为需要定义多个工厂类和产品类的层次结构。这可能会导致系统中类的数量增加。在选择使用工厂方法模式时,需要根据具体情况进行权衡。
工厂方法模式在实际应用中非常常见,例如,图形库可以使用工厂方法模式来创建不同类型的图形对象,数据库访问框架可以使用工厂方法模式来创建不同类型的数据库连接等。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 // 首先,我们需要定义一个图形接口 interface Shape { void draw(); } // 然后,我们实现两个具体的图形类,分别是 Circle(圆形)和 Rectangle(矩形) class Circle implements Shape { @Override public void draw() { System.out.println("Drawing a circle"); } } class Rectangle implements Shape { @Override public void draw() { System.out.println("Drawing a rectangle"); } } // 接下来,我们创建一个抽象工厂类 ShapeFactory // 它定义了一个抽象的工厂方法 createShape,子类将实现这个方法来创建具体的图形对象 abstract class ShapeFactory { abstract Shape createShape(); } // 然后,我们创建两个具体的工厂类,分别是 CircleFactory 和 RectangleFactory // 它们分别实现了 ShapeFactory 并重写了 createShape 方法来返回相应的图形对象 class CircleFactory extends ShapeFactory { @Override Shape createShape() { return new Circle(); } } class RectangleFactory extends ShapeFactory { @Override Shape createShape() { return new Rectangle(); } } // 我们可以使用这些工厂类来创建图形对象 public class FactoryMethodExample { public static void main(String[] args) { ShapeFactory circleFactory = new CircleFactory(); Shape circle = circleFactory.createShape(); circle.draw(); ShapeFactory rectangleFactory = new RectangleFactory(); Shape rectangle = rectangleFactory.createShape(); rectangle.draw(); } }
2. 抽象工厂模式(Abstract Factory) 问题 :
在某些情况下,需要创建一系列相关或相互依赖的对象,这些对象属于一组相关的产品族。同时,系统需要保证这些产品族之间的一致性。如果直接在代码中创建这些对象,会使得代码与具体产品的细节紧密耦合,不利于后续的扩展和维护。
解决方案 :
抽象工厂模式提供了一个接口,用于创建一系列相关或相互依赖的对象。通过使用抽象工厂接口及其具体实现,可以将对象的创建与客户端代码分离,从而实现系统的松耦合。抽象工厂模式涉及多个角色:
抽象工厂(Abstract Factory) :声明了一组用于创建不同产品的抽象方法。具体的工厂类必须实现这些方法来创建具体的产品对象。
具体工厂(Concrete Factory) :实现抽象工厂接口,负责创建特定种类的产品对象。
抽象产品(Abstract Product) :定义了产品的通用接口,具体产品必须实现这个接口。
具体产品(Concrete Product) :实现抽象产品接口,是抽象工厂创建的实际对象。
效果 :
抽象工厂模式的使用可以带来以下效果:
产品族一致性 :抽象工厂确保创建的产品是一组相关的产品族,保证了这些产品之间的一致性。
松耦合 :客户端代码不需要直接依赖于具体产品,只需要通过抽象工厂接口创建产品,从而降低了代码的耦合度。
可扩展性 :增加新的产品族或产品变得相对容易,只需要添加新的具体工厂和产品类即可,不需要修改现有代码。
限制 :抽象工厂模式要求系统中的每个产品族都必须有一个对应的具体工厂,这可能增加了系统的复杂性。
抽象工厂模式适用于需要创建一系列相关产品并保证它们之间一致性的情况,例如图形界面库中的 UI 元素,不同操作系统下的界面组件等。通过使用抽象工厂模式,可以更好地管理和组织这些产品的创建过程。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 // 抽象产品接口:操作系统 interface OperatingSystem { void run(); } // 具体产品:Windows操作系统 class WindowsOS implements OperatingSystem { @Override public void run() { System.out.println("Running Windows OS"); } } // 具体产品:Linux操作系统 class LinuxOS implements OperatingSystem { @Override public void run() { System.out.println("Running Linux OS"); } } // 抽象产品接口:应用程序 interface Application { void open(); } // 具体产品:Word应用程序 class WordApplication implements Application { @Override public void open() { System.out.println("Opening Word Application"); } } // 具体产品:Excel应用程序 class ExcelApplication implements Application { @Override public void open() { System.out.println("Opening Excel Application"); } } // 抽象工厂接口 interface SoftwareFactory { OperatingSystem createOperatingSystem(); Application createApplication(); } // 具体工厂:Windows工厂 class WindowsFactory implements SoftwareFactory { @Override public OperatingSystem createOperatingSystem() { return new WindowsOS(); } @Override public Application createApplication() { return new ExcelApplication(); } } // 具体工厂:Linux工厂 class LinuxFactory implements SoftwareFactory { @Override public OperatingSystem createOperatingSystem() { return new LinuxOS(); } @Override public Application createApplication() { return new WordApplication(); } } // 在这个示例中,抽象工厂模式通过SoftwareFactory接口和其实现类来创建不同类型的操作系统和应用程序。 // 客户端代码可以根据需要选择不同的工厂实例来创建不同的产品组合。 public class Client { public static void main(String[] args) { SoftwareFactory windowsFactory = new WindowsFactory(); OperatingSystem windowsOS = windowsFactory.createOperatingSystem(); Application windowsApp = windowsFactory.createApplication(); windowsOS.run(); windowsApp.open(); SoftwareFactory linuxFactory = new LinuxFactory(); OperatingSystem linuxOS = linuxFactory.createOperatingSystem(); Application linuxApp = linuxFactory.createApplication(); linuxOS.run(); linuxApp.open(); } }
3. 建造者模式(Builder) 问题 :
在某些情况下,一个对象的创建过程非常复杂,涉及多个步骤,每个步骤都可能有不同的实现方式。如果将所有创建逻辑放在一个类中,会导致该类变得庞大且难以维护。此外,如果需要创建不同的变体对象,就需要在该类中添加更多的逻辑,使得代码变得混乱。
解决方案 :
建造者模式提供了一种将一个复杂对象的构建过程与其表示分离的方法。它将对象的构建过程封装在一个独立的 “建造者” 类中,由该类负责逐步构建对象。这样,可以根据需要创建不同的建造者来构建不同的对象变体。通常,建造者模式涉及以下角色:
产品(Product) :表示正在构建的复杂对象。建造者模式的目标是构建这个产品。
抽象建造者(Abstract Builder) :定义了构建产品的步骤和方法,但没有具体的实现。不同的具体建造者可以实现不同的构建步骤,从而创建不同的产品变体。
具体建造者(Concrete Builder) :实现了抽象建造者定义的方法,完成了产品的构建过程。每个具体建造者负责构建特定的产品变体。
指导者(Director) :负责控制建造的过程。它通过将客户端与具体建造者分离,确保产品的构建是按照一定顺序和规则进行的。
效果 :
建造者模式的效果包括:
分离构建过程和表示 :通过建造者模式,可以将复杂对象的构建过程与其最终表示分离,使得构建过程更加清晰可控。
支持不同的表示 :通过使用不同的具体建造者,可以创建不同的产品表示,而不改变客户端的代码。
更好的可扩展性 :如果需要添加新的产品变体,只需创建一个新的具体建造者即可,而无需修改已有的代码。
隐藏产品的内部结构 :客户端只需与抽象建造者和指导者交互,无需关心产品的内部构建细节。
总之,建造者模式适用于需要构建复杂对象,且构建过程涉及多个步骤或变体的情况。通过将构建过程分解为可重用的步骤,建造者模式提供了一种结构化的方法来创建对象。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 // 首先,我们定义房屋类 House,它具有多个属性,如地基、结构、屋顶和装修。 class House { private String foundation; private String structure; private String roof; private String interior; public void setFoundation(String foundation) { this.foundation = foundation; } public void setStructure(String structure) { this.structure = structure; } public void setRoof(String roof) { this.roof = roof; } public void setInterior(String interior) { this.interior = interior; } @Override public String toString() { return "House [foundation=" + foundation + ", structure=" + structure + ", roof=" + roof + ", interior=" + interior + "]"; } } // 然后,我们创建一个抽象建造者类 HouseBuilder,它定义了构建房屋的方法。 abstract class HouseBuilder { protected House house = new House(); public abstract void buildFoundation(); public abstract void buildStructure(); public abstract void buildRoof(); public abstract void buildInterior(); public House getHouse() { return house; } } // 接下来,我们创建两个具体的建造者类 ConcreteHouseBuilder 和 LuxuryHouseBuilder // 分别实现了不同类型房屋的构建过程。 // 具体建造者类 - 普通房屋 class ConcreteHouseBuilder extends HouseBuilder { @Override public void buildFoundation() { house.setFoundation("Standard Foundation"); } @Override public void buildStructure() { house.setStructure("Standard Structure"); } @Override public void buildRoof() { house.setRoof("Standard Roof"); } @Override public void buildInterior() { house.setInterior("Standard Interior"); } } // 具体建造者类 - 豪华房屋 class LuxuryHouseBuilder extends HouseBuilder { @Override public void buildFoundation() { house.setFoundation("Strong Foundation"); } @Override public void buildStructure() { house.setStructure("Reinforced Structure"); } @Override public void buildRoof() { house.setRoof("Elegant Roof"); } @Override public void buildInterior() { house.setInterior("Luxury Interior"); } } // 最后,我们创建指导者类 Director,它协调建造过程并返回构建的房屋对象。 class Director { private HouseBuilder builder; public Director(HouseBuilder builder) { this.builder = builder; } public House constructHouse() { builder.buildFoundation(); builder.buildStructure(); builder.buildRoof(); builder.buildInterior(); return builder.getHouse(); } } // 这个示例演示了如何使用建造者模式创建不同类型的房屋,每种房屋类型的建造过程都由相应的具体建造者类负责实现,而指导者类负责协调建造过程。 public class BuilderPatternExample { public static void main(String[] args) { HouseBuilder concreteBuilder = new ConcreteHouseBuilder(); Director director1 = new Director(concreteBuilder); House concreteHouse = director1.constructHouse(); System.out.println("Concrete House: " + concreteHouse); HouseBuilder luxuryBuilder = new LuxuryHouseBuilder(); Director director2 = new Director(luxuryBuilder); House luxuryHouse = director2.constructHouse(); System.out.println("Luxury House: " + luxuryHouse); } }
4. 原型模式(Prototype) 问题 :
在某些情况下,需要创建对象的副本,但复制一个对象的成本可能很高,或者希望避免与对象的具体类耦合。例如,当创建对象的过程较为复杂,或者对象包含大量共享的状态时,使用常规的创建方法可能会导致性能下降。
解决方案 :
原型模式的解决方案是通过复制现有对象来创建新对象,而不是从头开始构建。这允许我们以更高效的方式创建新对象,同时避免了与对象类的直接耦合。核心概念是在原型对象的基础上进行克隆,使得新对象具有与原型相同的初始状态。
在原型模式中,通常会有以下几个角色:
抽象原型(Prototype) :声明克隆方法,作为所有具体原型的基类或接口。
具体原型(Concrete Prototype) :实现克隆方法,从自身创建一个副本。
客户端(Client) :使用原型对象的客户端代码,在需要新对象时通过克隆现有对象来创建新实例。
效果 :
原型模式的应用可以带来以下效果:
减少对象创建的成本 :避免了复杂对象的重复初始化过程,提高了创建对象的效率。
避免与具体类耦合 :客户端可以通过克隆方法创建新对象,而无需知道具体类的细节,降低了耦合度。
灵活性增加 :可以在运行时动态地添加或删除原型,适应不同的对象创建需求。
支持动态配置 :可以通过克隆来定制对象的不同配置,而无需修改其代码。
然而,也需要注意一些限制,如:
深克隆问题 :原型模式默认进行浅克隆,即复制对象本身和其引用。如果对象内部包含其他对象的引用,可能需要实现深克隆来复制整个对象结构。
克隆方法的实现 :某些对象可能不容易进行克隆,特别是涉及到文件、网络连接等资源的情况。
总之,原型模式是一种在需要创建对象副本时非常有用的设计模式,它提供了一种灵活且高效的方法来处理对象的复制需求。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 // 创建一个实现 Cloneable 接口的原型类 class Shape implements Cloneable { private String type; public Shape(String type) { this.type = type; } public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public Shape clone() { try { return (Shape) super.clone(); } catch (CloneNotSupportedException e) { return null; } } } // 测试原型模式 public class PrototypeExample { public static void main(String[] args) { // 创建原型对象 Shape circle = new Shape("Circle"); // 克隆原型对象来创建新对象 Shape clonedCircle = circle.clone(); clonedCircle.setType("Cloned Circle"); // 输出原型对象和克隆对象的类型 System.out.println("Original Shape Type: " + circle.getType()); System.out.println("Cloned Shape Type: " + clonedCircle.getType()); } }
5. 单例模式(Singleton) 问题:
在某些情况下,需要确保一个类只有一个实例,并且需要一个全局访问点来访问这个实例。例如,在一个应用程序中,一个配置管理器类需要保持一致的配置数据,以避免不同部分之间的配置冲突。
解决方案:
单例模式通过确保一个类只能创建一个实例,并提供一个静态方法或静态属性来访问这个实例。通常,单例类会将自己的构造函数声明为私有,以防止外部代码直接创建实例。通过一个静态方法,单例类可以控制在运行时只能获得同一个实例。
效果:
单例模式的应用可以确保在整个应用程序中只有一个实例存在,从而节省了资源和内存。它也可以提供一个全局的访问点,使得代码中的各个部分都可以方便地获取这个实例。然而,过度使用单例模式可能导致全局状态的难以控制,以及模块之间的紧耦合。在多线程环境下需要小心处理,以确保线程安全。
总之,单例模式是一种常用的设计模式,适用于需要全局唯一实例的场景。它的核心思想在于通过限制类的实例化来控制对象的数量,从而保证全局唯一性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class Singleton { // 私有静态成员变量,用于保存单例实例 private static Singleton instance; // 私有构造方法,防止外部实例化 private Singleton() { // 初始化操作 } // 公共静态方法,用于获取单例实例 public static Singleton getInstance() { if (instance == null) { // 如果实例为空,则创建一个新实例 instance = new Singleton(); } return instance; } // 其他成员方法 public void showMessage() { System.out.println("Hello, I am a Singleton!"); } } // 这个示例演示了如何创建一个简单的单例模式 // 但请注意,这个实现并不是线程安全的。 // 在多线程环境中,可能会出现多个线程同时访问getInstance()方法,导致创建多个实例的情况。 // 为了实现线程安全的单例模式,可以使用双重检查锁定或其他同步机制。 public class Main { public static void main(String[] args) { // 获取单例实例 Singleton singleton = Singleton.getInstance(); // 调用成员方法 singleton.showMessage(); } }
6. 适配器模式(Adapter) 问题 :
当你有两个不兼容的接口(即类或对象),但需要它们能够一起工作时,适配器模式可以解决这个问题。例如,你可能有一个已存在的类库或组件,但其接口与你的代码不匹配,你希望能够无缝地将它们集成在一起。
解决方案 :
适配器模式通过引入一个适配器类来充当中间人,将一个接口转换成另一个接口,使得两个不兼容的对象能够协同工作。适配器类包含一个对不兼容接口的引用,并实现了你期望的目标接口。这样,当你需要使用目标接口的时候,可以通过适配器来调用原本不兼容的类的方法。
效果 :
适配器模式的应用可以使得现有的代码与新代码能够无缝协同工作,从而提高了代码的可重用性。它允许你将不同系统、库或组件整合在一起,而无需对现有代码进行大量修改。然而,适配器模式也可能引入一些复杂性,因为你需要维护适配器类和处理不同接口之间的映射关系。
总的来说,适配器模式是一种很有用的模式,特别适合在集成不同组件或类时,解决接口不匹配的问题,从而保持代码的灵活性和可维护性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 // 已存在的LegacyRectangle类 class LegacyRectangle { public void display(int x1, int y1, int x2, int y2) { System.out.println("LegacyRectangle: Point1(" + x1 + ", " + y1 + "), Point2(" + x2 + ", " + y2 + ")"); } } // 统一的Shape接口 interface Shape { void draw(int x, int y, int width, int height); } // 适配器类,将LegacyRectangle适配到Shape接口上 class RectangleAdapter implements Shape { private LegacyRectangle legacyRectangle; public RectangleAdapter(LegacyRectangle legacyRectangle) { this.legacyRectangle = legacyRectangle; } @Override public void draw(int x, int y, int width, int height) { int x1 = x; int y1 = y; int x2 = x + width; int y2 = y + height; legacyRectangle.display(x1, y1, x2, y2); } } // 在这个示例中,LegacyRectangle是已经存在的类,而RectangleAdapter是适配器类,用于将LegacyRectangle适配到Shape接口上。 // 客户端代码通过使用适配器来画一个矩形,实际上是在调用了LegacyRectangle的display方法,但是通过适配器,它符合了Shape接口的标准。 public class AdapterPatternExample { public static void main(String[] args) { LegacyRectangle legacyRectangle = new LegacyRectangle(); Shape shapeAdapter = new RectangleAdapter(legacyRectangle); shapeAdapter.draw(10, 20, 50, 30); } }
7. 桥接模式(Bridge) 问题 :
在软件设计中,有时候你会遇到一个类有多个变化维度(例如抽象和具体的实现)。如果使用继承来处理这些变化,将会导致类层次结构的急剧增加,难以管理和维护。此外,继承会将抽象部分和具体部分紧密耦合,不利于独立地进行扩展和变化。
解决方案 :
桥接模式通过将抽象部分和具体部分分离,使它们可以独立地变化。在桥接模式中,通过创建一个桥接接口(或抽象类),其中包含一个指向具体实现的引用,将抽象部分和具体部分连接起来。这样,抽象部分和具体部分可以独立地进行扩展,而不会相互影响。这种方式也被称为 “组合优于继承”。
效果 :
桥接模式的应用能够提供更好的灵活性和可扩展性。它允许抽象部分和具体部分独立变化,避免了类层次结构的爆炸式增长。这样可以更容易地添加新的抽象部分和具体部分,而不会影响到彼此。然而,使用桥接模式可能会引入一些复杂性,因为你需要管理更多的类和对象。
总之,桥接模式是一种有助于解耦抽象和实现,提供更灵活、可扩展设计的设计模式。它适用于那些需要处理多个变化维度的情况,同时又希望保持代码的清晰结构和可维护性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 // 实现部分 - 颜色接口 interface Color { void applyColor(); } class Red implements Color { public void applyColor() { System.out.println("Applying red color"); } } class Blue implements Color { public void applyColor() { System.out.println("Applying blue color"); } } // 抽象部分 - 形状类 abstract class Shape { protected Color color; public Shape(Color color) { this.color = color; } abstract void draw(); } class Circle extends Shape { public Circle(Color color) { super(color); } public void draw() { System.out.print("Drawing a circle. "); color.applyColor(); } } class Square extends Shape { public Square(Color color) { super(color); } public void draw() { System.out.print("Drawing a square. "); color.applyColor(); } } // 在这个示例中,Color 接口代表颜色的实现部分,Red 和 Blue 分别是实现了颜色接口的具体颜色类。 // Shape 是形状的抽象部分,具有一个颜色引用,而 Circle 和 Square 是继承自 Shape 的具体形状类。 // 这种设计允许我们在不改变形状或颜色的情况下,独立地对它们进行扩展和变化。 public class BridgePatternExample { public static void main(String[] args) { Color redColor = new Red(); Color blueColor = new Blue(); Shape redCircle = new Circle(redColor); Shape blueSquare = new Square(blueColor); redCircle.draw(); blueSquare.draw(); } }
8. 组合模式(Composite) 问题:
在某些情况下,我们需要处理一组对象,这些对象之间具有整体 - 部分的关系。我们希望能够以一致的方式处理单个对象和对象组合,而不需要对它们进行特殊处理。
解决方案:
组合模式的解决方案是将对象组合成树状结构,其中树的节点可以是单个对象或对象组合。这样,无论是操作单个对象还是对象组合,都可以使用统一的方式进行操作。组合模式通过定义一个共同的抽象类或接口来表示单个对象和对象组合,从而实现了透明的处理。
在组合模式中,通常有两种主要角色:
组件(Component): 这是一个抽象类或接口,定义了单个对象和对象组合共同的操作。它可以有一些默认实现,也可以有抽象方法需要在具体子类中实现。
叶子(Leaf): 继承自组件,表示单个对象。它没有子对象。
复合(Composite): 继承自组件,表示对象组合。它包含了一组子对象,这些子对象可以是叶子,也可以是复合。
效果:
组合模式的优点包括:
透明性: 使用组合模式,客户端可以一致地对待单个对象和对象组合,无需关心具体对象的类型。
简化客户端代码: 客户端不需要判断操作的对象是单个对象还是对象组合,从而简化了客户端的代码。
灵活性: 可以很方便地添加新的叶子或复合对象,扩展性较好。
然而,组合模式也可能带来一些限制和权衡,如:
不适合所有情况: 并非所有情况都适合使用组合模式。在一些情况下,可能会引入不必要的复杂性。
可能限制操作: 组合模式可能会限制某些特定对象的操作,因为共同的抽象接口可能无法涵盖所有可能的操作。
综上所述,组合模式适用于处理对象的整体 - 部分关系,并且能够提供一种统一、透明的方式来处理这些对象,从而提高代码的可维护性和扩展性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 // 组件接口 interface FileSystemComponent { void displayInfo(); } // 叶子节点 class File implements FileSystemComponent { private String name; public File(String name) { this.name = name; } public void displayInfo() { System.out.println("File: " + name); } } // 容器节点 class Directory implements FileSystemComponent { private String name; private List<FileSystemComponent> components; public Directory(String name) { this.name = name; components = new ArrayList<>(); } public void addComponent(FileSystemComponent component) { components.add(component); } public void displayInfo() { System.out.println("Directory: " + name); for (FileSystemComponent component : components) { component.displayInfo(); } } } // 在这个示例中,FileSystemComponent 是组合模式的组件接口,File 是叶子节点类,而 Directory 是容器节点类。 // 通过使用这些类,我们可以构建一个具有层次结构的文件系统。 // 注意:这只是一个简单的示例,真实的组合模式可能涉及更复杂的场景和更多的功能。 public class CompositePatternExample { public static void main(String[] args) { // 创建文件和文件夹 File file1 = new File("file1.txt"); File file2 = new File("file2.txt"); Directory subDirectory = new Directory("Subdirectory"); subDirectory.addComponent(file1); subDirectory.addComponent(file2); Directory rootDirectory = new Directory("Root"); rootDirectory.addComponent(subDirectory); // 展示文件系统结构 rootDirectory.displayInfo(); } }
9. 装饰模式(Decorator) 问题 :
在某些情况下,我们需要在不修改现有对象结构的情况下,动态地添加功能或责任。继承在这种情况下可能会导致类爆炸问题,而且修改现有类可能会影响到其他部分的代码。
解决方案 :
装饰模式提供了一种在运行时动态地为对象添加新功能的方法,通过创建一个装饰类来包装原始类。装饰类具有与原始类相同的接口,它内部包含一个指向原始对象的引用,并且可以根据需要包装额外的功能。这样,你可以通过组合不同的装饰类来构建出具有不同功能组合的对象。
效果 :
装饰模式的优点包括避免了类爆炸问题,因为你可以通过组合少量的装饰类来实现各种功能组合。它也使得功能的增加和修改更加灵活,不会影响到其他部分的代码。然而,装饰模式可能会导致增加很多小型的类,从而增加了代码的复杂性。
在装饰模式中,通常涉及以下角色:
组件(Component) :定义了一个抽象的接口,可以是具体对象或装饰器所共有的接口。
具体组件(Concrete Component) :实现了组件接口,是被装饰的原始对象。
装饰器(Decorator) :持有一个指向组件对象的引用,并实现了组件的接口。它可以包含额外的功能,也可以将请求传递给组件对象。
具体装饰器(Concrete Decorator) :扩展了装饰器类,通过添加额外的功能来装饰具体组件。
通过这种方式,装饰模式允许你将功能嵌套地堆叠在一起,以实现各种不同的功能组合,同时保持代码的灵活性和可维护性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 // 首先定义一个咖啡接口 interface Coffee { double cost(); String description(); } // 实现基本的咖啡类 class SimpleCoffee implements Coffee { @Override public double cost() { return 2.0; } @Override public String description() { return "Simple Coffee"; } } // 创建装饰器抽象类 abstract class CoffeeDecorator implements Coffee { protected Coffee decoratedCoffee; public CoffeeDecorator(Coffee coffee) { this.decoratedCoffee = coffee; } @Override public double cost() { return decoratedCoffee.cost(); } @Override public String description() { return decoratedCoffee.description(); } } // 实现具体的装饰器类 class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee coffee) { super(coffee); } @Override public double cost() { return super.cost() + 1.0; } @Override public String description() { return super.description() + ", with Milk"; } } class SugarDecorator extends CoffeeDecorator { public SugarDecorator(Coffee coffee) { super(coffee); } @Override public double cost() { return super.cost() + 0.5; } @Override public String description() { return super.description() + ", with Sugar"; } } // 在这个示例中,Coffee 接口定义了基本的咖啡功能。SimpleCoffee 类实现了基本的咖啡。 // CoffeeDecorator 是装饰器的抽象类,它维护一个被装饰的咖啡对象。 // MilkDecorator 和 SugarDecorator 分别实现了具体的装饰器,通过在原始咖啡上添加新的功能。 public class DecoratorPatternExample { public static void main(String[] args) { Coffee simpleCoffee = new SimpleCoffee(); System.out.println("Cost: $" + simpleCoffee.cost() + ", Description: " + simpleCoffee.description()); Coffee milkCoffee = new MilkDecorator(simpleCoffee); System.out.println("Cost: $" + milkCoffee.cost() + ", Description: " + milkCoffee.description()); Coffee sugarMilkCoffee = new SugarDecorator(milkCoffee); System.out.println("Cost: $" + sugarMilkCoffee.cost() + ", Description: " + sugarMilkCoffee.description()); } }
10. 外观模式(Facade) 问题 :
在软件开发中,系统可能变得非常复杂,包含多个子系统和各种交互。这些子系统之间的依赖关系和调用可能变得混乱,导致系统难以理解、扩展和维护。在这种情况下,我们需要一种方法来提供一个简单的接口,将复杂的子系统调用和依赖关系进行封装,使客户端能够更轻松地与系统进行交互。
解决方案 :
外观模式通过引入一个外观类(Facade),将复杂的子系统接口进行封装,为客户端提供一个简单的高层接口。外观类充当了客户端与子系统之间的中间人,处理客户端的请求并将其转发给适当的子系统。外观模式并不在系统中添加新功能,它只是提供了一个更简洁的接口,以简化客户端的操作。
效果 :
外观模式的应用可以带来以下效果:
简化接口 :客户端只需要与外观类交互,无需了解底层子系统的复杂性。
降低耦合 :外观模式将客户端与子系统解耦,使得系统的变化不会影响客户端代码。
提高可维护性 :由于外观模式将子系统封装起来,修改子系统的实现不会影响客户端代码,从而提高了系统的可维护性。
支持松散耦合 :外观模式可以帮助系统中的不同模块之间实现松散耦合,从而支持模块的独立开发和测试。
总之,外观模式通过提供一个简化的接口,将复杂的子系统封装起来,帮助提高系统的可用性、可维护性和灵活性。它在处理复杂系统的同时,使客户端代码更加清晰和易于理解。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 // 子系统:音响 class StereoSystem { public void turnOn() { System.out.println("Stereo System is turned on"); } public void turnOff() { System.out.println("Stereo System is turned off"); } } // 子系统:投影仪 class Projector { public void turnOn() { System.out.println("Projector is turned on"); } public void turnOff() { System.out.println("Projector is turned off"); } } // 子系统:灯光控制 class LightsControl { public void turnOn() { System.out.println("Lights are turned on"); } public void turnOff() { System.out.println("Lights are turned off"); } } // 外观类:家庭影院外观 class HomeTheaterFacade { private StereoSystem stereo; private Projector projector; private LightsControl lights; public HomeTheaterFacade() { stereo = new StereoSystem(); projector = new Projector(); lights = new LightsControl(); } public void watchMovie() { System.out.println("Getting ready to watch a movie..."); lights.turnOff(); projector.turnOn(); stereo.turnOn(); } public void endMovie() { System.out.println("Ending the movie..."); stereo.turnOff(); projector.turnOff(); lights.turnOn(); } } // HomeTheaterFacade充当了一个外观类,封装了音响、投影仪和灯光控制等子系统的复杂操作,以便客户端可以通过简单的调用来完成观影过程。 // 这样,客户端不需要了解各个子系统的具体操作,只需通过外观类的方法来控制整个家庭影院系统的行为。 public class FacadeExample { public static void main(String[] args) { HomeTheaterFacade homeTheater = new HomeTheaterFacade(); // 准备观影 homeTheater.watchMovie(); // 结束观影 homeTheater.endMovie(); } }
11. 享元模式(Flyweight) 问题 :
在某些情况下,一个应用程序可能需要大量相似对象,而这些对象的大部分属性是相同的。在这种情况下,创建大量相似对象会占用大量的内存和系统资源,导致系统性能下降。
解决方案 :
享元模式的解决方案是共享对象的状态,以减少内存和资源的消耗。它将对象分为两部分:内部状态(Intrinsic State)和外部状态(Extrinsic State)。内部状态是对象共享的部分,而外部状态是每个对象特有的部分。
享元模式通过一个享元工厂(Flyweight Factory)来管理和创建共享对象。当需要一个对象时,工厂会检查是否已经有相同内部状态的对象存在,如果存在则返回已有的对象,否则创建一个新的对象并将其添加到内部对象池中。
效果 :
优点 :享元模式可以显著减少内存消耗,因为共享对象的内部状态只有一份。这可以在需要大量相似对象的情况下节省内存。同时,由于共享对象已经存在于池中,创建时间和性能开销也会降低。
权衡 :享元模式引入了内部状态和外部状态的区分,这可能增加了系统的复杂性。此外,对内部状态的共享需要考虑线程安全性。
限制 :享元模式适用于对象的内部状态相对稳定,而外部状态会变化的情况。如果一个对象的状态完全相同,那么不需要使用享元模式。
可能的后果 :通过减少对象的创建和内存占用,系统性能可能会得到提升。但在一些情况下,过度使用享元模式可能会引入不必要的复杂性,因此需要根据具体情况进行权衡。
享元模式在需要大量相似对象的场景中非常有用,例如文字处理软件中的字符对象、图像处理软件中的像素对象等。它可以显著提高系统的性能和资源利用率。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 // 享元接口 interface Shape { void draw(int x, int y); } // 具体享元类 class Circle implements Shape { private Color color; public Circle(Color color) { this.color = color; } @Override public void draw(int x, int y) { System.out.println("Drawing a " + color + " circle at (" + x + "," + y + ")"); } } // 享元工厂类 class ShapeFactory { private static final Map<Color, Shape> circleMap = new HashMap<>(); public static Shape getCircle(Color color) { Shape circle = circleMap.get(color); if (circle == null) { circle = new Circle(color); circleMap.put(color, circle); } return circle; } } // 在这个示例中,我们定义了一个Shape接口和一个具体的Circle类来表示享元对象。 // ShapeFactory类负责管理共享的对象池,并通过getCircle方法返回共享的或新创建的圆形对象。 // 在main函数中,我们随机选择不同的颜色,并使用ShapeFactory获取对应的圆形对象,然后调用draw方法绘制它们。 public class FlyweightPatternExample { public static void main(String[] args) { Color[] colors = {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW}; for (int i = 0; i < 20; i++) { Color randomColor = colors[(int) (Math.random() * colors.length)]; Shape circle = ShapeFactory.getCircle(randomColor); circle.draw((int) (Math.random() * 100), (int) (Math.random() * 100)); } } }
12. 代理模式(Proxy) 问题:
在某些情况下,我们希望通过一个中间代理来控制对某个对象的访问。这可能是因为原始对象的创建或访问涉及复杂的逻辑,或者我们想要在访问原始对象之前或之后执行一些操作。
解决方案:
代理模式提供了一个代理对象,它充当了原始对象的替代品,以控制对原始对象的访问。代理对象与原始对象实现相同的接口,使得客户端可以无缝地切换和使用。代理对象可以对客户端的请求进行拦截、修改或增强,然后将请求传递给原始对象。
效果:
代理模式的应用可以带来多种效果:
远程代理(Remote Proxy): 代理对象可以隐藏原始对象存在于远程服务器上的事实,使得客户端可以透明地访问远程对象。这对于分布式系统非常有用。
虚拟代理(Virtual Proxy): 当创建原始对象需要大量资源时,代理对象可以充当一个轻量级的替代品,延迟原始对象的实际创建和初始化,从而提高性能。
保护代理(Protection Proxy): 代理对象可以控制对原始对象的访问权限,确保只有具有特定权限的客户端可以访问原始对象。
缓存代理(Cache Proxy): 代理对象可以缓存原始对象的结果,以便在后续相同请求时能够直接返回缓存的结果,减少重复计算。
日志记录代理(Logging Proxy): 代理对象可以在访问原始对象之前或之后记录日志,用于调试、监控或审计。
总之,代理模式允许我们在不改变原始对象的情况下,通过引入代理对象来添加额外的控制和功能。这有助于提高代码的可维护性、可扩展性和灵活性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 // 图像接口 interface Image { void display(); } // 真实图像类 class RealImage implements Image { private String filename; public RealImage(String filename) { this.filename = filename; loadImageFromDisk(); } private void loadImageFromDisk() { System.out.println("Loading image from disk: " + filename); } public void display() { System.out.println("Displaying image: " + filename); } } // 代理图像类 class ProxyImage implements Image { private RealImage realImage; private String filename; public ProxyImage(String filename) { this.filename = filename; } public void display() { if (realImage == null) { realImage = new RealImage(filename); } realImage.display(); } } // 在这个示例中,Image接口定义了display方法,RealImage是实际的图像加载类,而ProxyImage是代理图像类。 // 当ProxyImage的display方法被调用时,它会在需要时创建一个RealImage实例,并调用其display方法。 public class ProxyPatternExample { public static void main(String[] args) { Image image = new ProxyImage("sample.jpg"); // 图像未加载,直到调用display()方法 image.display(); // 图像已加载,无需再次创建 image.display(); } }
13. 解释器模式(Interpreter) 问题 :
在某些情况下,你可能需要解释和处理一种特定语言或表达式。这可能涉及到解析、分析和执行这些语言或表达式,但在每个具体情况下,解释的方式都可能不同。
解决方案 :
解释器模式通过定义一种语言文法的表示,并提供一种解释器来解释这种语言的语句。这样,你可以将语句表示为抽象语法树,然后通过解释器逐步执行和解释这个语法树。
抽象表达式(Abstract Expression) :定义了一个抽象的解释方法,所有的具体表达式都需要实现这个接口。
终结符表达式(Terminal Expression) :实现了抽象表达式接口,用于表示语言中的终结符(最小的语法单元)。
非终结符表达式(Non-terminal Expression) :实现了抽象表达式接口,用于表示语言中的非终结符,通常由多个终结符和 / 或其他非终结符组成的组合。
上下文(Context) :包含了需要被解释的信息,通常包括输入的语句和解释器。
解释器(Interpreter) :包含了解释器模式的主要逻辑,它通过递归的方式对抽象语法树进行解释,实现了语言中各种语句的解释和执行。
效果 :
解释器模式的使用可以使你更容易地实现特定语言的解释和执行,尤其在处理自定义的领域特定语言(DSL)时非常有用。然而,解释器模式可能导致类的数量增加,因为每个语法规则都需要一个相应的表达式类。此外,解释器模式可能会对性能产生影响,特别是在处理复杂语法时。
总之,解释器模式适用于需要解释和处理特定语言或表达式的情况,它通过将语句表示为抽象语法树并提供解释器来执行解释。这有助于实现定制的语言处理逻辑。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 // 表达式接口 interface Expression { int interpret(); } // 数字表达式类 class NumberExpression implements Expression { private int value; public NumberExpression(int value) { this.value = value; } @Override public int interpret() { return value; } } // 加法表达式类 class AddExpression implements Expression { private Expression leftOperand; private Expression rightOperand; public AddExpression(Expression leftOperand, Expression rightOperand) { this.leftOperand = leftOperand; this.rightOperand = rightOperand; } @Override public int interpret() { return leftOperand.interpret() + rightOperand.interpret(); } } // 减法表达式类 class SubtractExpression implements Expression { private Expression leftOperand; private Expression rightOperand; public SubtractExpression(Expression leftOperand, Expression rightOperand) { this.leftOperand = leftOperand; this.rightOperand = rightOperand; } @Override public int interpret() { return leftOperand.interpret() - rightOperand.interpret(); } } // 在这个示例中,我们构建了一个简单的数学表达式解释器,用于解释并计算基本的加法和减法表达式。 // 这展示了解释器模式如何工作,将表达式解释成实际的结果。 // 在实际应用中,解释器模式可以用于更复杂的领域,如编程语言解释器或规则引擎。 public class InterpreterPatternExample { public static void main(String[] args) { // 构建表达式:2 + (3 - 1) Expression expression = new AddExpression( new NumberExpression(2), new SubtractExpression( new NumberExpression(3), new NumberExpression(1) ) ); // 解释并计算表达式的值 int result = expression.interpret(); System.out.println("Result: " + result); // 输出: Result: 4 } }
14. 模板方法模式(Template Method) 问题:
当你在设计一个类或一组类时,发现有一些算法的结构是固定的,但其中的某些步骤可能会因应用情境或子类的不同而变化。你希望将这个算法的核心结构固定下来,但留出一些灵活性来允许特定步骤的定制。
解决方案:
模板方法模式通过定义一个抽象的父类,其中包含了算法的核心结构,但某些步骤使用抽象方法或受保护的虚拟方法来表示,这些方法由子类来实现。这使得子类可以根据需要重写特定的步骤,而核心算法结构保持不变。父类中的模板方法调用这些步骤,确保算法的整体流程一致。
效果:
模板方法模式的效果包括:
代码复用: 核心算法结构在父类中定义,可以被多个子类共享,避免了重复的代码。
灵活性: 子类可以通过实现特定的步骤来定制算法的行为,而不需要改变算法的整体结构。
可维护性: 将算法的核心结构集中在一个地方,易于维护和修改。
代码一致性: 所有子类共享相同的算法模板,确保了算法的一致性。
示例:
想象你正在设计一个咖啡和茶的准备流程。虽然两者的基本步骤相似(烧水、冲泡、添加调味品等),但是每种饮料的具体步骤略有不同。你可以使用模板方法模式来创建一个饮料准备的抽象类,其中包含烧水、冲泡和倒入杯中等通用步骤,但将冲泡的细节留给子类来实现(如茶类和咖啡类)。
这样,你就能在不改变整体流程的情况下,让不同的饮料类定制它们的冲泡过程。这遵循了模板方法模式的思想,将共享的算法结构与可变的部分分离,以便实现代码的重用和灵活性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 // 模板类 abstract class AbstractClass { // 模板方法,定义算法的骨架 public void templateMethod() { step1(); step2(); step3(); } // 基本方法,子类需要实现 abstract void step1(); abstract void step2(); abstract void step3(); } // 具体子类实现 class ConcreteClass extends AbstractClass { @Override void step1() { System.out.println("ConcreteClass: Step 1"); } @Override void step2() { System.out.println("ConcreteClass: Step 2"); } @Override void step3() { System.out.println("ConcreteClass: Step 3"); } } // 在上面的示例中,AbstractClass 是模板类,定义了一个包含三个步骤的模板方法 templateMethod // 这些步骤由抽象方法 step1、step2 和 step3 构成。ConcreteClass 是具体子类,继承自 AbstractClass,它实现了基本方法来完成每个步骤的具体行为。 // 在 main 方法中,我们创建了一个 ConcreteClass 实例并调用了 templateMethod,这会按照模板的结构执行具体的步骤。 public class TemplateMethodExample { public static void main(String[] args) { AbstractClass template = new ConcreteClass(); template.templateMethod(); } }
15. 责任链模式(Chain of Responsibility) 问题 :
在某些情况下,一个请求需要在多个对象之间传递,每个对象都可能处理该请求或将其传递给下一个对象。在这种情况下,需要避免将发送者与接收者之间的耦合,以及确定请求的处理方式。问题在于如何设计一个机制,使得多个对象都有机会处理请求,而且可以根据需要动态地改变它们之间的顺序和职责。
解决方案 :
责任链模式提供了一种通过一系列处理对象来处理请求的方法。每个处理对象都包含一个对下一个处理对象的引用,形成一个链式结构。当一个请求到达时,它首先被传递给链中的第一个处理对象,如果该对象不能处理该请求,它会将请求传递给下一个处理对象,依此类推,直到找到能够处理请求的对象为止。
责任链模式的解决方案包括以下关键点:
定义一个抽象处理者(Handler)类,该类包含一个对下一个处理者的引用,并声明一个处理请求的方法。
具体的处理者类继承自抽象处理者类,实现处理请求的方法。在该方法中,处理者可以决定是否处理请求,如果不能处理,则将请求传递给下一个处理者。
客户端创建一个处理链,将处理者按照一定的顺序连接起来。
效果 :
责任链模式的应用可以带来多个效果:
降低耦合度 :发送者不需要知道哪个对象会处理请求,只需将请求发送到链的起始点。
灵活性 :可以根据需要动态地改变处理链中处理者的顺序,以及每个处理者的职责。
可扩展性 :可以很容易地添加新的处理者,而不会影响现有代码。
可维护性 :每个处理者关注单一的责任,使得代码更易于理解和维护。
然而,责任链模式也有一些潜在的限制,比如可能导致请求无法被处理或者处理链太长而导致性能问题。因此,在使用责任链模式时需要谨慎权衡权衡利弊。
总之,责任链模式是一种有助于将请求与处理者解耦,并支持动态调整处理顺序和职责的设计模式。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 // 首先,我们需要创建一个表示请求的类 ReimbursementRequest public class ReimbursementRequest { private double amount; private String description; public ReimbursementRequest(double amount, String description) { this.amount = amount; this.description = description; } public double getAmount() { return amount; } public String getDescription() { return description; } } // 然后,创建一个抽象处理者类 ReimbursementHandler public abstract class ReimbursementHandler { protected ReimbursementHandler successor; public void setSuccessor(ReimbursementHandler successor) { this.successor = successor; } public abstract void handleRequest(ReimbursementRequest request); } // 接下来,实现具体的处理者类:经理、部门主管和财务部门处理者。 public class ManagerHandler extends ReimbursementHandler { @Override public void handleRequest(ReimbursementRequest request) { if (request.getAmount() <= 1000) { System.out.println("经理处理报销请求:" + request.getDescription()); } else if (successor != null) { successor.handleRequest(request); } } } public class DepartmentHeadHandler extends ReimbursementHandler { @Override public void handleRequest(ReimbursementRequest request) { if (request.getAmount() <= 5000) { System.out.println("部门主管处理报销请求:" + request.getDescription()); } else if (successor != null) { successor.handleRequest(request); } } } public class FinanceHandler extends ReimbursementHandler { @Override public void handleRequest(ReimbursementRequest request) { System.out.println("财务部门处理报销请求:" + request.getDescription()); } } // 在这个示例中,报销请求会依次被经理、部门主管和财务部门处理。根据报销金额的不同,请求会被传递到适当的处理者。 public class Main { public static void main(String[] args) { ReimbursementHandler manager = new ManagerHandler(); ReimbursementHandler departmentHead = new DepartmentHeadHandler(); ReimbursementHandler finance = new FinanceHandler(); manager.setSuccessor(departmentHead); departmentHead.setSuccessor(finance); ReimbursementRequest request1 = new ReimbursementRequest(800, "购买办公用品"); ReimbursementRequest request2 = new ReimbursementRequest(3000, "参加培训"); ReimbursementRequest request3 = new ReimbursementRequest(10000, "举办团建活动"); manager.handleRequest(request1); manager.handleRequest(request2); manager.handleRequest(request3); } }
16. 命令模式(Command) 问题 :
在某些情况下,你希望将请求发送者与接收者解耦,从而允许您以不同的方式组织和处理请求。例如,您可能希望将请求排队、记录、撤消或重做,而无需修改发送者和接收者之间的代码。
解决方案 :
命令模式提供了一种将请求封装成对象的方法,使得请求的发送者与请求的接收者之间不直接耦合。这通过引入以下角色实现:
命令(Command) :抽象命令类,定义了执行命令的接口。它通常包含一个执行方法,以及可能的其他方法(例如,撤消)。
具体命令(Concrete Command) :实现了抽象命令类的具体子类,将一个接收者与一个动作绑定。它实现了执行方法,该方法调用接收者的特定操作。
接收者(Receiver) :执行实际工作的类。命令模式将命令传递给接收者,由接收者执行实际的操作。
调用者 / 请求者(Invoker) :负责将命令传递给合适的接收者并触发命令的执行。它并不关心具体的命令细节。
客户端(Client) :创建命令对象、接收者对象以及调用者对象,并将它们组织起来以实现特定的操作流程。
效果 :
命令模式的效果在于解耦命令的发送者和接收者,从而支持更灵活的代码组织。它允许您轻松地添加新的命令,排队命令,记录命令历史,甚至实现撤消和重做功能。然而,命令模式也可能引入一些复杂性,因为您需要为每个操作创建一个具体命令类。
总的来说,命令模式在需要解耦请求发送者和接收者,并支持灵活的命令处理时非常有用。它在菜单系统、GUI 操作、多级撤销等场景中得到广泛应用。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 // 命令接口 interface Command { void execute(); } // 具体命令:控制电灯打开 class LightOnCommand implements Command { private Light light; public LightOnCommand(Light light) { this.light = light; } @Override public void execute() { light.turnOn(); } } // 具体命令:控制电灯关闭 class LightOffCommand implements Command { private Light light; public LightOffCommand(Light light) { this.light = light; } @Override public void execute() { light.turnOff(); } } // 电灯类 class Light { void turnOn() { System.out.println("Light is on"); } void turnOff() { System.out.println("Light is off"); } } // 遥控器类 class RemoteControl { private Command command; public void setCommand(Command command) { this.command = command; } public void pressButton() { command.execute(); } } // 在这个示例中,我们使用命令模式创建了两种具体的命令:打开电灯和关闭电灯。 // 遥控器可以设置不同的命令,然后按下按钮触发相应的操作。 // 这样,命令发送者(遥控器)和命令接收者(电灯)之间实现了解耦。 public class CommandPatternExample { public static void main(String[] args) { Light livingRoomLight = new Light(); LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight); RemoteControl remote = new RemoteControl(); remote.setCommand(livingRoomLightOn); remote.pressButton(); // 打开电灯 remote.setCommand(livingRoomLightOff); remote.pressButton(); // 关闭电灯 } }
17. 迭代器模式(Iterator) 问题 :
在软件开发中,经常需要遍历集合(如列表、数组、树等)中的元素,但不同集合可能有不同的遍历方式,这导致在客户端代码中需要编写不同的遍历逻辑,使代码变得复杂且难以维护。此外,有时候还需要在遍历过程中支持添加、删除等操作,这可能会影响遍历的一致性和正确性。
解决方案 :
迭代器模式提供了一种统一的方法来遍历不同类型的集合,而无需暴露集合内部的表示细节。它包括两个主要组件:迭代器和集合。迭代器负责遍历集合并提供统一的访问接口,而集合负责实际存储元素。迭代器和集合之间的解耦使得可以独立地改变它们的实现,而不会影响到客户端代码。
效果 :
优点:迭代器模式将遍历操作封装在迭代器中,使客户端代码更加简洁、可读,并且降低了与集合的耦合。它也提供了支持多种遍历方式的灵活性,如正向遍历、逆向遍历等。
权衡:迭代器模式可能会增加一些额外的类和接口,可能会稍微增加复杂性,但从长远来看,可以提高代码的可维护性和可扩展性。
限制:迭代器模式并不适用于所有情况。在一些简单的情况下,直接使用语言内置的遍历机制可能更为方便。
总之,迭代器模式提供了一种解决集合遍历问题的通用方法,使得代码更具结构和可维护性。它在各种编程语言和应用中都有广泛的应用。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 // 定义一个可迭代的集合接口 interface IterableCollection<T> { Iterator<T> createIterator(); } // 具体的集合类实现可迭代的集合接口 class ConcreteCollection<T> implements IterableCollection<T> { private List<T> items = new ArrayList<>(); public void addItem(T item) { items.add(item); } @Override public Iterator<T> createIterator() { return new ConcreteIterator<>(items); } } // 定义迭代器接口 interface Iterator<T> { boolean hasNext(); T next(); } // 具体迭代器实现迭代器接口 class ConcreteIterator<T> implements Iterator<T> { private List<T> items; private int position = 0; public ConcreteIterator(List<T> items) { this.items = items; } @Override public boolean hasNext() { return position < items.size(); } @Override public T next() { if (hasNext()) { T item = items.get(position); position++; return item; } throw new IndexOutOfBoundsException("No more elements"); } } // 在这个示例中,我们定义了一个IterableCollection接口来表示可迭代的集合,一个具体的集合类ConcreteCollection实现了这个接口,并提供了一个用于创建迭代器的方法。 // 迭代器接口Iterator定义了hasNext和next方法,具体的迭代器类ConcreteIterator实现了这个接口,并通过内部的位置追踪来遍历集合。 public class IteratorPatternExample { public static void main(String[] args) { ConcreteCollection<String> collection = new ConcreteCollection<>(); collection.addItem("Item 1"); collection.addItem("Item 2"); collection.addItem("Item 3"); Iterator<String> iterator = collection.createIterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
问题 :
在一个系统中,对象之间的通信可能会变得复杂,导致对象之间相互依赖,难以管理和维护。当对象之间的通信变得混乱时,就需要一个方法来将通信逻辑集中管理,从而减少耦合度并提高系统的可维护性。
解决方案 :
中介者模式引入了一个中介者对象,它负责协调和管理对象之间的通信。对象不再直接与其他对象通信,而是通过中介者来发送和接收消息。这样一来,对象只需要关注自己的职责,而不需要了解其他对象的详细信息。中介者模式的核心思想是将复杂的交互逻辑集中到一个地方,以便更好地管理和调整。
效果 :
降低耦合度 :对象之间的通信逻辑被集中在中介者中,从而降低了对象之间的直接依赖,减少了耦合度,使系统更加灵活和可维护。
集中管理 :所有对象的交互逻辑都集中在中介者中,使得系统的交互逻辑更加清晰可见,便于管理和修改。
复用性 :中介者模式将交互逻辑与对象本身的业务逻辑分离,可以更容易地复用这些交互逻辑。
可扩展性 :通过增加或修改中介者对象,可以相对容易地扩展系统,而不需要修改对象之间的通信逻辑。
需要注意的是,中介者模式可能会引入一个单一的中心化点,如果设计不当,可能会导致中介者对象本身变得过于复杂。因此,在使用中介者模式时,需要权衡考虑系统的复杂性和灵活性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 // 中介者接口 interface ChatMediator { void sendMessage(String message, User user); void addUser(User user); } // 具体中介者类 class ConcreteChatMediator implements ChatMediator { private List<User> users = new ArrayList<>(); @Override public void sendMessage(String message, User user) { for (User u : users) { if (u != user) { u.receiveMessage(message); } } } @Override public void addUser(User user) { users.add(user); } } // 用户类 class User { private String name; private ChatMediator mediator; public User(String name, ChatMediator mediator) { this.name = name; this.mediator = mediator; } public void sendMessage(String message) { System.out.println(name + " 发送消息: " + message); mediator.sendMessage(message, this); } public void receiveMessage(String message) { System.out.println(name + " 收到消息: " + message); } } // 在这个示例中,ConcreteChatMediator 实现了 ChatMediator 接口,并管理用户列表。 // 每个用户对象在构造时都传递了中介者实例,以便用户可以使用中介者发送和接收消息。 public class MediatorPatternExample { public static void main(String[] args) { ConcreteChatMediator chatMediator = new ConcreteChatMediator(); User user1 = new User("Alice", chatMediator); User user2 = new User("Bob", chatMediator); User user3 = new User("Charlie", chatMediator); chatMediator.addUser(user1); chatMediator.addUser(user2); chatMediator.addUser(user3); user1.sendMessage("大家好!"); user2.sendMessage("你好,Alice!"); } }
19. 备忘录模式(Memento) 问题 :
在软件设计中,经常会遇到需要记录一个对象的内部状态,并在需要时能够回滚到先前的状态。这可能是为了实现撤销操作、历史记录功能等。
解决方案 :
备忘录模式通过引入 “备忘录” 对象,允许在不暴露对象内部结构的情况下,捕获并存储对象的状态。同时,它还提供了一种将对象恢复到之前状态的方式。备忘录模式包括以下角色:
Originator(发起人) :这是需要被记录状态的对象。它创建一个备忘录对象,以存储当前状态,也可以从备忘录中恢复状态。
Memento(备忘录) :备忘录对象用于存储 Originator 的状态。通常,备忘录对象具有与原始对象相同的接口,但不会直接暴露其内部状态。
Caretaker(负责人) :负责管理备忘录对象。它可以存储多个备忘录对象,以便在需要时进行状态恢复。
效果 :
备忘录模式使得对象的状态管理更加灵活。它允许对象在不暴露其内部结构的情况下进行状态的保存和恢复。这有助于实现撤销和重做功能,以及历史记录和快照功能。然而,使用备忘录模式可能会增加一些内存开销,特别是如果需要存储大量的状态历史。
总之,备忘录模式在需要记录和恢复对象状态的情况下是一个有用的设计模式。它可以帮助保持代码的清晰性和可维护性,同时提供强大的状态管理功能。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 // 备忘录类 class Memento { private String state; public Memento(String state) { this.state = state; } public String getState() { return state; } } // 原始对象类 class Originator { private String state; public void setState(String state) { this.state = state; } public String getState() { return state; } public Memento createMemento() { return new Memento(state); } public void restoreMemento(Memento memento) { state = memento.getState(); } } // 管理者类 class Caretaker { private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } } // 在这个示例中,Originator 类表示原始对象,它具有状态并能够创建和恢复备忘录。 // Memento 类表示备忘录对象,保存了特定时刻的状态。Caretaker 类负责保存和获取备忘录对象。 // 通过设置初始状态、创建备忘录、修改状态、然后恢复状态,我们可以看到备忘录模式的工作方式。 public class MementoPatternExample { public static void main(String[] args) { Originator originator = new Originator(); Caretaker caretaker = new Caretaker(); // 设置初始状态 originator.setState("State 1"); System.out.println("Current State: " + originator.getState()); // 创建备忘录并保存状态 caretaker.setMemento(originator.createMemento()); // 修改状态 originator.setState("State 2"); System.out.println("Updated State: " + originator.getState()); // 恢复之前的状态 originator.restoreMemento(caretaker.getMemento()); System.out.println("Restored State: " + originator.getState()); } }
20. 观察者模式(Observer) 问题 :
在软件设计中,经常会遇到这样的情况:一个对象(主题)的状态发生改变,而其他对象(观察者)需要在状态改变时得到通知并进行相应的更新。但是,如果直接在对象之间建立硬编码的依赖关系,会导致系统的耦合度增加,难以维护和扩展。观察者模式试图解决这个问题,允许主题和观察者之间的松耦合通信。
解决方案 :
观察者模式的核心思想是定义一种一对多的依赖关系,使得一个主题(通常称为被观察者)可以同时维护多个观察者,并在其状态改变时自动通知所有观察者。这样,观察者无需关心主题的内部实现细节,而只需要关心主题的状态变化。在实现中,通常会定义一个抽象的主题类和一个抽象的观察者类,具体的主题和观察者类会继承这些抽象类并实现相应的方法。
效果 :
观察者模式的应用有以下优点:
松耦合 :主题和观察者之间的耦合度降低,使得它们可以独立地进行变化。
可扩展性 :可以方便地增加新的观察者,而不会影响到已有的观察者和主题。
自动通知 :主题状态改变时会自动通知观察者,减少手动维护通知的工作。
可重用性 :主题和观察者可以在不同的场景中重复使用。
然而,观察者模式也有一些限制和权衡:
可能引起性能问题 :如果观察者过多或通知机制不合理,可能会导致性能下降。
更新顺序问题 :观察者的更新顺序可能会影响到系统的行为,需要特别注意。
过度使用的风险 :并不是所有的状态变化都适合使用观察者模式,过度使用可能导致代码复杂化。
总之,观察者模式是一种用于解决对象间状态通知和更新的重要设计模式,它在许多软件系统中都有广泛的应用。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 import java.util.ArrayList; import java.util.List; // 主题接口 interface Subject { void addObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(); } // 具体主题类 class ConcreteSubject implements Subject { private List<Observer> observers = new ArrayList<>(); private int state; public int getState() { return state; } public void setState(int state) { this.state = state; notifyObservers(); } @Override public void addObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { for (Observer observer : observers) { observer.update(state); } } } // 观察者接口 interface Observer { void update(int state); } // 具体观察者类 class ConcreteObserver implements Observer { private String name; public ConcreteObserver(String name) { this.name = name; } @Override public void update(int state) { System.out.println(name + " 收到更新,新状态为: " + state); } } // 在这个示例中,ConcreteSubject 充当主题(被观察者),ConcreteObserver 充当观察者。 // 主题维护一个观察者列表,并在状态变化时通知所有观察者。 // 当主题的状态发生变化时,所有观察者都会被通知并更新自己的状态。 public class ObserverPatternExample { public static void main(String[] args) { ConcreteSubject subject = new ConcreteSubject(); Observer observer1 = new ConcreteObserver("观察者1"); Observer observer2 = new ConcreteObserver("观察者2"); subject.addObserver(observer1); subject.addObserver(observer2); subject.setState(10); subject.setState(20); subject.removeObserver(observer1); subject.setState(30); } }
21. 状态模式(State) 问题:
当一个对象的行为在不同状态下发生改变,并且对象需要根据其状态执行不同的操作时,就可以考虑使用状态模式。在这种情况下,如果直接在对象内部实现所有状态之间的切换逻辑,会导致代码变得复杂且难以维护。
解决方案:
状态模式的解决方案是将对象的状态抽象成独立的状态类,每个状态类都实现了一组特定状态下的操作。然后,上下文对象(即包含状态的对象)维护一个指向当前状态的引用,通过委托给当前状态的方法来执行操作。这种方式可以将不同状态下的行为逻辑分隔开来,使得状态变化时的代码修改更加容易。
效果:
使用状态模式可以实现以下效果:
清晰的状态切换: 状态模式将每个状态的行为集中在各自的状态类中,使得状态切换的逻辑变得清晰,易于管理和修改。
可维护性: 将状态相关的代码分布在不同的状态类中,使得代码更加模块化和可维护。
扩展性: 添加新的状态只需要创建新的状态类并实现相关操作,不会影响到其他状态类或上下文类的代码。
避免条件语句: 状态模式避免了大量的条件语句,从而提高了代码的可读性和可维护性。
复用性: 状态类之间的逻辑可以被复用,因为它们是独立的实体。
总之,状态模式使得对象在不同状态下能够更加灵活地切换行为,同时保持了代码的可维护性和可扩展性。它在需要处理复杂状态逻辑的情况下特别有用。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 // 状态接口 interface ElevatorState { void openDoors(); void closeDoors(); void move(); void stop(); } // 具体状态类:开门状态 class OpenState implements ElevatorState { @Override public void openDoors() { System.out.println("Doors are already open."); } @Override public void closeDoors() { System.out.println("Closing doors."); } @Override public void move() { System.out.println("Cannot move while doors are open."); } @Override public void stop() { System.out.println("Stopping while doors are open."); } } // 具体状态类:关门状态 class CloseState implements ElevatorState { @Override public void openDoors() { System.out.println("Opening doors."); } @Override public void closeDoors() { System.out.println("Doors are already closed."); } @Override public void move() { System.out.println("Moving."); } @Override public void stop() { System.out.println("Stopping."); } } // 上下文类:电梯 class Elevator { private ElevatorState state; public Elevator() { state = new CloseState(); // 初始状态为关门状态 } public void setState(ElevatorState state) { this.state = state; } public void openDoors() { state.openDoors(); } public void closeDoors() { state.closeDoors(); } public void move() { state.move(); } public void stop() { state.stop(); } } // 在这个示例中,我们创建了一个模拟电梯系统,其中有开门状态和关门状态两个具体状态类,以及电梯类作为上下文类。 // 通过切换状态,电梯在不同状态下有不同的行为表现。这就是状态模式的基本思想。 public class StatePatternExample { public static void main(String[] args) { Elevator elevator = new Elevator(); elevator.openDoors(); // 当前状态:开门 elevator.move(); // 当前状态:开门,无法移动 elevator.closeDoors(); // 当前状态:关门 elevator.move(); // 当前状态:移动中 elevator.stop(); // 当前状态:停止 elevator.openDoors(); // 当前状态:开门 } }
22. 策略模式(Strategy) 问题 :
在某些情况下,一个软件系统可能需要根据不同的情境或条件使用不同的算法或行为,但是这些算法的选择和使用可能会频繁变化。如果将这些算法都硬编码在主要的类中,会导致代码的臃肿不堪,难以维护和扩展。需要一种方式来灵活地选择和切换不同的算法,同时又不影响到客户端代码。
解决方案 :
策略模式提供了一种定义一系列算法的方法,将这些算法封装成独立的策略类,并使它们可以相互替换。在客户端中,创建一个上下文(Context)对象,该对象包含一个对策略类的引用,通过该引用调用相应的策略方法。这样,客户端可以在运行时选择不同的策略,而不需要修改上下文类。
效果 :
策略模式的主要优点是实现了算法的解耦,使得算法可以独立于客户端而变化。它提高了代码的可维护性和扩展性,因为新的策略可以很容易地添加到系统中。然而,策略模式也可能导致类的数量增加,因为每个算法都需要一个对应的策略类。在使用策略模式时,需要权衡类的数量与灵活性之间的关系。
总之,策略模式是一种非常有用的设计模式,特别适用于需要根据情境灵活选择不同算法或行为的场景,帮助保持代码的结构清晰且易于维护。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 // 首先,我们定义一个接口 MathOperation,表示数学操作的策略 // 定义策略接口 interface MathOperation { int operate(int a, int b); } // 实现加法策略 class Addition implements MathOperation { @Override public int operate(int a, int b) { return a + b; } } // 实现减法策略 class Subtraction implements MathOperation { @Override public int operate(int a, int b) { return a - b; } } // 实现乘法策略 class Multiplication implements MathOperation { @Override public int operate(int a, int b) { return a * b; } } // 然后,我们创建一个 Calculator 类,它接受一个数学操作策略,并根据用户的选择执行相应的操作 class Calculator { private MathOperation operation; public void setOperation(MathOperation operation) { this.operation = operation; } public int performOperation(int a, int b) { if (operation != null) { return operation.operate(a, b); } throw new IllegalStateException("No operation set"); } } // 在这个示例中,我们通过创建不同的数学操作策略类来实现加法、减法和乘法功能,并通过设置不同的策略来执行不同的操作。这就是策略模式的基本思想。 public class StrategyPatternExample { public static void main(String[] args) { Calculator calculator = new Calculator(); calculator.setOperation(new Addition()); int result1 = calculator.performOperation(5, 3); System.out.println("Addition Result: " + result1); calculator.setOperation(new Subtraction()); int result2 = calculator.performOperation(10, 4); System.out.println("Subtraction Result: " + result2); calculator.setOperation(new Multiplication()); int result3 = calculator.performOperation(6, 2); System.out.println("Multiplication Result: " + result3); } }
23. 访问者模式(Visitor) 问题 :
在面向对象设计中,当一个对象结构中的元素类(例如,不同类型的对象)需要进行多种不同的操作时,常常会导致操作与元素的类相耦合,从而难以扩展新的操作而不影响现有的类。此外,每次添加新的操作都需要修改已存在的元素类。
解决方案 :
访问者模式提出了一种解决方案,使得可以在不修改元素类的情况下,将操作从元素类中分离出来。它的核心思想是引入一个称为 “访问者” 的接口或类,该访问者包含了多个访问操作,每个操作对应一个元素类。元素类接受访问者,从而将自身传递给访问者,使得访问者可以对元素执行相应的操作。
效果 :
分离关注点 :访问者模式将元素类与具体操作分离,使得每个类可以专注于自身的职责,而操作则由访问者来实现。
易于扩展 :添加新的操作只需要增加一个新的访问者,不需要修改已存在的元素类,因此对系统的扩展更加容易。
可维护性 :由于每个操作被封装在独立的访问者中,使得代码更加清晰、易于维护。
灵活性 :可以在不修改元素类的情况下,动态地添加新的操作。
不适用于频繁变化的元素类 :如果元素类经常发生变化,会导致频繁修改访问者接口和实现。
总之,访问者模式适用于需要对一组不同类型的对象执行多种不同操作的情况。它在维护、扩展和修改代码时提供了更好的灵活性和可维护性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 // 首先,我们需要定义图形形状的接口和具体类 // 图形形状接口 interface Shape { void accept(ShapeVisitor visitor); } // 圆形类 class Circle implements Shape { private double radius; public Circle(double radius) { this.radius = radius; } public double getRadius() { return radius; } @Override public void accept(ShapeVisitor visitor) { visitor.visit(this); } } // 矩形类 class Rectangle implements Shape { private double width; private double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } public double getWidth() { return width; } public double getHeight() { return height; } @Override public void accept(ShapeVisitor visitor) { visitor.visit(this); } } // 接下来,定义一个访问者接口和具体的访问者实现 // 访问者接口 interface ShapeVisitor { void visit(Circle circle); void visit(Rectangle rectangle); } // 面积计算访问者 class AreaCalculator implements ShapeVisitor { private double area; @Override public void visit(Circle circle) { area += Math.PI * circle.getRadius() * circle.getRadius(); } @Override public void visit(Rectangle rectangle) { area += rectangle.getWidth() * rectangle.getHeight(); } public double getArea() { return area; } } // 在这个示例中,访问者模式允许我们在不修改形状类的情况下,通过实现不同的访问者来执行不同的操作,例如计算面积。 // 这样,我们可以轻松地添加新的访问者来执行其他操作,同时保持形状类的不变。 public class VisitorPatternExample { public static void main(String[] args) { Circle circle = new Circle(5); Rectangle rectangle = new Rectangle(4, 6); AreaCalculator areaCalculator = new AreaCalculator(); circle.accept(areaCalculator); rectangle.accept(areaCalculator); System.out.println("Total area: " + areaCalculator.getArea()); } }