参考文章23种设计模式(Java版,超详细!) ,菜鸟教程-设计模式
设计模式介绍 设计模式的优点
提供了一种共享的设计词汇和概念,使开发人员能够更好地沟通和理解彼此的设计意图。
提供了经过验证的解决方案,可以提高软件的可维护性、可复用性和灵活性 。
促进了代码的重用,避免了重复的设计和实现。
通过遵循设计模式,可以减少系统中的错误和问题,提高代码质量。
设计模式六大原则
开闭原则(Open Close Principle):
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
里氏代换原则(Liskov Substitution Principle):
里氏代换原则是面向对象设计的基本原则之一。
里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。
里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤 就是抽象化 ,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
依赖倒转原则(Dependence Inversion Principle):
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
接口隔离原则(Interface Segregation Principle):
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。
它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
迪米特法则,又称最少知道原则(Demeter Principle):
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
合成复用原则(Composite Reuse Principle):
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
设计模式概览
创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象 。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活
工厂方法模式(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 interface Shape { void draw () ; } 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" ); } } abstract class ShapeFactory { abstract Shape 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(); } }
抽象工厂模式(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 interface OperatingSystem { void run () ; } class WindowsOS implements OperatingSystem { @Override public void run () { System.out.println("Running Windows OS" ); } } class LinuxOS implements OperatingSystem { @Override public void run () { System.out.println("Running Linux OS" ); } } interface Application { void open () ; } class WordApplication implements Application { @Override public void open () { System.out.println("Opening Word Application" ); } } class ExcelApplication implements Application { @Override public void open () { System.out.println("Opening Excel Application" ); } } interface SoftwareFactory { OperatingSystem createOperatingSystem () ; Application createApplication () ; } class WindowsFactory implements SoftwareFactory { @Override public OperatingSystem createOperatingSystem () { return new WindowsOS (); } @Override public Application createApplication () { return new ExcelApplication (); } } class LinuxFactory implements SoftwareFactory { @Override public OperatingSystem createOperatingSystem () { return new LinuxOS (); } @Override public Application createApplication () { return new WordApplication (); } } 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(); } }
建造者模式(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 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 + "]" ; } } 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; } } 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" ); } } 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); } }
原型模式(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 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()); } }
单例模式(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 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!" ); } } public class Main { public static void main (String[] args) { Singleton singleton = Singleton.getInstance(); singleton.showMessage(); } }
适配器模式(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 class LegacyRectangle { public void display (int x1, int y1, int x2, int y2) { System.out.println("LegacyRectangle: Point1(" + x1 + ", " + y1 + "), Point2(" + x2 + ", " + y2 + ")" ); } } interface Shape { void draw (int x, int y, int width, int height) ; } 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); } } public class AdapterPatternExample { public static void main (String[] args) { LegacyRectangle legacyRectangle = new LegacyRectangle (); Shape shapeAdapter = new RectangleAdapter (legacyRectangle); shapeAdapter.draw(10 , 20 , 50 , 30 ); } }
桥接模式(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 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(); } } 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(); } }
组合模式(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 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(); } } } 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(); } }
装饰模式(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 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" ; } } 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()); } }
外观模式(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 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(); } } public class FacadeExample { public static void main (String[] args) { HomeTheaterFacade homeTheater = new HomeTheaterFacade (); homeTheater.watchMovie(); homeTheater.endMovie(); } }
享元模式(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 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; } } 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 )); } } }
代理模式(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 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(); } } public class ProxyPatternExample { public static void main (String[] args) { Image image = new ProxyImage ("sample.jpg" ); image.display(); image.display(); } }
解释器模式(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 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) { Expression expression = new AddExpression ( new NumberExpression (2 ), new SubtractExpression ( new NumberExpression (3 ), new NumberExpression (1 ) ) ); int result = expression.interpret(); System.out.println("Result: " + result); } }
模板方法模式(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 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" ); } } public class TemplateMethodExample { public static void main (String[] args) { AbstractClass template = new ConcreteClass (); template.templateMethod(); } }
责任链模式(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 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; } } 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); } }
命令模式(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 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(); } }
迭代器模式(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 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" ); } } 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 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); } } 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!" ); } }
备忘录模式(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 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; } } 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()); } }
观察者模式(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 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); } } 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 ); } }
状态模式(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 100 101 102 103 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(); if (!(state instanceof OpenState)) { setState(new OpenState ()); } } public void closeDoors () { state.closeDoors(); if (!(state instanceof CloseState)) { setState(new CloseState ()); } } 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(); } }
策略模式(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 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; } } 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); } }
访问者模式(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 / 首先,我们需要定义图形形状的接口和具体类 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()); } }