技术漫谈 大话设计模式 FANSEA 2024-07-19 2025-03-13 大话设计模式 基础知识 - 设计模式概述 - 《设计模式 Java版本》 - 书栈网 · BookStack
(Pattern Name)通过一两个词来描述模式的问题、解决方案和效果,以便更好地理解模式并方便开发人员之间的交流,绝大多数模式都是根据其功能或模式结构来命名的(GoF设计模式中没有一个模式用人名命名,微笑);问题(Problem)描述了应该在何时使用模式,它包含了设计中存在的问题以及问题存在的原因;解决方案(Solution)描述了一个设计模式的组成成分,以及这些组成成分之间的相互关系,各自的职责和协作方式,通常解决方案通过UML类图和核心代码来进行描述;效果(Consequences)描述了模式的优缺点以及在使用模式时应权衡的问题。
迪米特法则
https://www.bilibili.com/video/BV1Ge41157dY/?spm_id_from=333.337.search-card.all.click&vd_source=70a5c913e74574ad96afc2ae210ba3e0
迪米特法则,也称为最少知识原则(Law of Demeter),是面向对象设计中的一个原则,旨在降低对象之间的耦合性,提高系统的可维护性和可扩展性。该原则强调一个类不应该直接与其它不相关的类相互交互,而是通过少数几个密切相关的类来进行通信 。这有助于减少类之间的依赖关系,降低代码的耦合性,使得系统更加灵活和易于维护。
迪米特法则的核心思想可以概括为以下几点:
一个对象应该尽量少地了解其他对象的内部结构和实现。
一个对象只与其直接朋友(即与其关联最密切的对象)进行交互。
避免在一个类中引入不必要的依赖关系,尽量保持类之间的解耦。
策略模式
侧重行为,不同方法解决不同问题,if-else
组成:
行为接口
实现具体的行为类
定义行为发生的类,该类用于注入并执行行为
第一步:定义行为接口
1 2 3 public interface QuackBehavior { void quack () ; }
第二步:实现具体的行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Quack implements QuackBehavior { @Override public void quack () { System.out.println("Quack" ); } } public class Squeak implements QuackBehavior { @Override public void quack () { System.out.println("Squeak" ); } } public class MuteQuack implements QuackBehavior { @Override public void quack () { System.out.println("<< Silence >>" ); } }
第三步:定义鸭子类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Duck { private QuackBehavior quackBehavior; public Duck (QuackBehavior quackBehavior) { this .quackBehavior = quackBehavior; } public void performQuack () { quackBehavior.quack(); } public void setQuackBehavior (QuackBehavior quackBehavior) { this .quackBehavior = quackBehavior; } }
第四步:创建测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class DuckTestDrive { public static void main (String[] args) { Duck duck1 = new Duck (new Quack ()); Duck duck2 = new Duck (new Squeak ()); Duck duck3 = new Duck (new MuteQuack ()); System.out.println("Duck 1:" ); duck1.performQuack(); System.out.println("\nDuck 2:" ); duck2.performQuack(); System.out.println("\nDuck 3:" ); duck3.performQuack(); System.out.println("\nChanging behavior for Duck 1:" ); duck1.setQuackBehavior(new Squeak ()); duck1.performQuack(); } }
运行结果:
1 2 3 4 5 6 7 8 9 10 11 Duck 1: Quack Duck 2: Squeak Duck 3: << Silence >> Changing behavior for Duck 1: Squeak
适配器模式
多个方法解决同一个问题
将原有的接口变成客户需要的另外一种接口
目标接口(target) :需要适配的标准接口。
需要适配的类(adaptee) :需要被适配的不兼容对象。
适配器对象(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 interface Target { void request () ; } class Adaptee { void specificRequest () { System.out.println("Adaptee's specific request" ); } } class Adapter extends Adaptee implements Target { @Override public void request () { specificRequest(); } } public class Client { public static void main (String[] args) { Target target = new Adapter (); target.request(); } }
模板方法
使用场景
当你有个通用的流程需要多个类共享,但某些步骤需要根据具体情况进行定义,可以考虑模板方法设计模式
与策略模式的差异
模板方法:侧重流程(框架)的共用,具体细节的替换
策略模式:侧重行为的多样性,关注算法的可替换性
样例演示
需要处理不同类型的数据文件(如文本文件、图片文件等)。我们可以定义一个抽象的基类 FileProcessor
,该类包含一个模板方法 processFile
,该方法定义了处理文件的基本步骤。具体的文件处理逻辑由子类来实现。
首先,我们定义一个抽象的基类 FileProcessor
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public abstract class FileProcessor { protected String fileName; public FileProcessor (String fileName) { this .fileName = fileName; } public final void processFile () { loadFile(); transformData(); saveData(); } protected abstract void loadFile () ; protected abstract void transformData () ; protected abstract void saveData () ; }
然后,我们定义两个具体的子类来处理不同的文件类型:
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 public class TextFileProcessor extends FileProcessor { public TextFileProcessor (String fileName) { super (fileName); } @Override protected void loadFile () { System.out.println("Loading text file: " + fileName); } @Override protected void transformData () { System.out.println("Transforming text data" ); } @Override protected void saveData () { System.out.println("Saving transformed text data" ); } } public class ImageFileProcessor extends FileProcessor { public ImageFileProcessor (String fileName) { super (fileName); } @Override protected void loadFile () { System.out.println("Loading image file: " + fileName); } @Override protected void transformData () { System.out.println("Transforming image data" ); } @Override protected void saveData () { System.out.println("Saving transformed image data" ); } }
最后,我们可以在主函数中创建不同的处理器对象并调用 processFile
方法:
1 2 3 4 5 6 7 8 9 10 11 public class Main { public static void main (String[] args) { FileProcessor textProcessor = new TextFileProcessor ("example.txt" ); textProcessor.processFile(); FileProcessor imageProcessor = new ImageFileProcessor ("example.jpg" ); imageProcessor.processFile(); } }
在这个例子中,FileProcessor
类定义了一个模板方法 processFile
,它包含了三个步骤:loadFile
、transformData
和 saveData
。这些步骤的具体实现由子类 TextFileProcessor
和 ImageFileProcessor
提供。通过这种方式,我们能够保证处理文件的整体流程一致,同时允许不同的子类实现具体的细节。
代理模式
强调通过代理对象来对对象控制,中心不在于装饰
代理模式 :关注控制对象的访问 ,通常与对象的生命周期管理相关。
装饰器模式 :关注动态地为对象添加功能 ,通常与对象的行为增强相关。
装饰器模式
强调动态的添加新的功能,与代理模式很相似,其实是借鉴了代理模式的思路,
代理模式的与其的不同在于:被代理对象的注入可以通过类动态加载来实现,而不只是注入对象
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许你动态地给对象添加一些额外的职责,即在不需要通过子类的情况下扩展其功能。这种模式经常被用作替代继承的一个方法,提供比继承更加灵活的替代方案 。
下面是一个简单的Java示例来展示装饰器模式的应用:
假设我们有一个Beverage
接口,定义了饮料的基本行为,例如计算价格。
1 2 3 4 public interface Beverage { String getDescription () ; double cost () ; }
然后我们有一些实现了Beverage
接口的具体类,比如HouseBlend
:
1 2 3 4 5 6 7 8 9 10 11 public class HouseBlend implements Beverage { @Override public String getDescription () { return "House Blend Coffee" ; } @Override public double cost () { return 0.89 ; } }
接下来,我们创建一个抽象的装饰器类,它也实现了Beverage
接口,并且包含了一个对Beverage
类型的引用。这个装饰器可以用来包装任何实现了Beverage
接口的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public abstract class CondimentDecorator implements Beverage { private Beverage beverage; public CondimentDecorator (Beverage beverage) { this .beverage = beverage; } @Override public String getDescription () { return beverage.getDescription(); } @Override public double cost () { return beverage.cost(); } }
现在我们可以创建具体的装饰器类来为饮料添加不同的特性,比如加糖浆的装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Syrup extends CondimentDecorator { public Syrup (Beverage beverage) { super (beverage); } @Override public String getDescription () { return beverage.getDescription() + ", Syrup" ; } @Override public double cost () { return 0.15 + beverage.cost(); } }
最后,我们可以在客户端代码中使用这些类:
1 2 3 4 5 6 7 8 9 10 public class TestDecorator { public static void main (String[] args) { Beverage beverage = new HouseBlend (); System.out.println(beverage.getDescription() + " $" + beverage.cost()); beverage = new Syrup (beverage); System.out.println(beverage.getDescription() + " $" + beverage.cost()); } }
在这个例子中,Syrup
装饰器可以被用来给任何实现了Beverage
接口的对象添加糖浆,而不需要修改原始的Beverage
类。这样就提供了很大的灵活性,可以方便地组合不同的装饰器来得到所需的最终对象。
备忘录模式
备忘录模式(Memento Pattern)是一种行为设计模式,它允许你在不破坏封装性的前提下捕获一个对象的内部状态,并在稍后恢复该对象之前的状态。这种模式常用于实现撤销(Undo)功能,或者当需要保存和恢复对象的状态时。
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 class EditorMemento { private final String content; public EditorMemento (String content) { this .content = content; } public String getContent () { return content; } } class Editor { private String content; public EditorMemento save () { return new EditorMemento (this .content); } public void restore (EditorMemento memento) { this .content = memento.getContent(); } public void setContent (String content) { this .content = content; } public String getContent () { return this .content; } } class EditorHistory { private List<EditorMemento> states = new ArrayList <>(); public void push (EditorMemento state) { states.add(state); } public EditorMemento pop () { return states.remove(states.size() - 1 ); } }
备忘当前实体:
1 2 3 Editor editor = new Editor ();EditorMemento editorMemento = editor.save();new EditorHistory ().push(editorMemento);
桥接模式
它将抽象部分与它的实现部分分离 ,使得它们都可以独立地变化。这种模式可以用来动态地给一个对象添加功能,而不需要通过继承来实现 。
TIP:一个操作对应多个实现,但这个多个实现也可能依赖于多个实现
场景 :假设我们需要创建一个图形库,支持不同类型的图形(如圆形、矩形等),并且能够在不同的平台上(如Windows、Linux等)绘制这些图形。这里我们就可以使用桥接模式来解决这个问题。
画图为一个操作、不同形状对应不同的实现 :
shape:
1 2 3 4 5 6 7 public abstract class Shape { protected GraphicsAPI api; public Shape (GraphicsAPI api) { this .api = api; } public abstract void draw () ; }
shape的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Circle extends Shape { public Circle (GraphicsAPI api) { super (api); } @Override public void draw () { api.draw(this ); } } public class Rectangle extends Shape { public Rectangle (GraphicsAPI api) { super (api); } @Override public void draw () { api.draw(this ); } }
多平台适配总接口:
1 2 3 public interface GraphicsAPI { void draw (Shape shape) ; }
多平台适配实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class WindowsAPI implements GraphicsAPI { @Override public void draw (Shape shape) { System.out.println("Drawing " + shape.getClass().getSimpleName() + " using Windows API" ); } } public class LinuxAPI implements GraphicsAPI { @Override public void draw (Shape shape) { System.out.println("Drawing " + shape.getClass().getSimpleName() + " using Linux API" ); } }
使用:
1 2 3 4 5 6 7 8 9 10 11 12 public class Main { public static void main (String[] args) { GraphicsAPI winApi = new WindowsAPI (); GraphicsAPI linuxApi = new LinuxAPI (); Shape circleWin = new Circle (winApi); Shape rectLinux = new Rectangle (linuxApi); circleWin.draw(); rectLinux.draw(); } }
状态模式
它允许对象在其内部状态改变时改变其行为。对象看起来似乎修改了它的类。
用途:
当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
控制大量条件语句,当这些条件判断依赖于对象的状态时。
1 假设我们有一个自动售货机,它有三种状态:`等待投币`(WaitingForCoin)、`已经投币`(CoinInserted)、`商品被选中`(ProductSelected)。售货机可以根据用户的操作改变状态,并相应地执行不同的行为。
观察者模式
发布订阅模式
观察者模式在Java中有广泛的应用,尤其是在事件处理中。以下是一个简单的Java示例来说明观察者模式:
首先,我们定义一个Subject
接口,代表被观察的对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.util.ArrayList;import java.util.List;public interface Subject { void registerObserver (Observer o) ; void removeObserver (Observer o) ; void notifyObservers () ; void setState (String state) ; String getState () ; }
接下来,我们实现Subject
接口的RealSubject
类:
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 import java.util.ArrayList;import java.util.List;public class RealSubject implements Subject { private List<Observer> observers; private String state; public RealSubject () { observers = new ArrayList <>(); } @Override public void registerObserver (Observer o) { observers.add(o); } @Override public void removeObserver (Observer o) { int i = observers.indexOf(o); if (i >= 0 ) { observers.remove(i); } } @Override public void notifyObservers () { for (Observer observer : observers) { observer.update(this ); } } @Override public void setState (String state) { this .state = state; notifyObservers(); } @Override public String getState () { return state; } }
然后,我们定义Observer
接口,代表观察者:
1 2 3 4 public interface Observer { void update (Subject subject) ; }
接着,我们实现Observer
接口的BinaryObserver
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class BinaryObserver implements Observer { @Override public void update (Subject subject) { System.out.println("BinaryObserver: State is " + subject.getState()); } } public class HexObserver implements Observer { @Override public void update (Subject subject) { System.out.println("HexObserver: State is " + subject.getState()); } }
最后,我们编写测试代码来演示观察者模式的工作过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ObserverPatternDemo { public static void main (String[] args) { RealSubject realSubject = new RealSubject (); Observer binaryObserver = new BinaryObserver (); Observer hexObserver = new HexObserver (); realSubject.registerObserver(binaryObserver); realSubject.registerObserver(hexObserver); realSubject.setState("1101" ); realSubject.setState("1010" ); realSubject.removeObserver(hexObserver); realSubject.setState("1111" ); } }
在上面的代码中,RealSubject
是被观察的对象,BinaryObserver
和HexObserver
(这里未给出实现)是观察者。当RealSubject
的状态改变时,它会调用notifyObservers()
方法来通知所有注册的观察者。每个观察者通过实现update()
方法来响应状态的改变。
在实际应用中,观察者模式可以用于多种场景,比如GUI编程中的事件监听、数据库连接的监听、用户界面的变化监听等。它使得对象之间的依赖关系解耦,降低了系统的耦合度,提高了代码的可维护性和可扩展性。
责任链设计模式
适用于流水线任务,对每一个环节进行类的定义,顺序执行,可以保障流程扩展性
组成 :
Handler(环节类):包含执行当前环节任务的方法,以及下一个环节的指针
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 52 53 54 55 56 57 58 59 60 61 62 63 64 interface Handler { void setNext (Handler handler) ; Object handle (Object request) ; } abstract class AbstractHandler implements Handler { private Handler next; @Override public void setNext (Handler handler) { this .next = handler; } @Override public Object handle (Object request) { if (this .next != null ) { return this .next.handle(request); } return null ; } } class ConcreteHandler1 extends AbstractHandler { @Override public Object handle (Object request) { if ((Integer) request < 10 ) { System.out.println("Request handled in ConcreteHandler1" ); } else { super .handle(request); } return request; } } class ConcreteHandler2 extends AbstractHandler { @Override public Object handle (Object request) { if ((Integer) request >= 10 && (Integer) request < 20 ) { System.out.println("Request handled in ConcreteHandler2" ); } else { super .handle(request); } return request; } } public class ChainOfResponsibilityDemo { public static void main (String[] args) { Handler handler1 = new ConcreteHandler1 (); Handler handler2 = new ConcreteHandler2 (); handler1.setNext(handler2); for (int i = 0 ; i <= 25 ; i += 5 ) { handler1.handle(i); } } }
在这个例子中,ConcreteHandler1
和 ConcreteHandler2
分别处理不同范围的请求值。当一个请求进入链后,每个处理者检查是否满足它的条件,如果满足则处理请求,否则将请求传给下一个处理者。这样就形成了一个处理请求的责任链。
注意:在实际应用中,需要确保责任链的末尾处理者能够妥善处理无法识别的请求,以避免无限递归的问题。在上面的例子中,如果没有处理者接受请求,则默认返回null
。