技术漫谈 大话设计模式 FANSEA 2024-07-19 2024-11-28 大话设计模式 基础知识 - 设计模式概述 - 《设计模式 Java版本》 - 书栈网 · BookStack
(Pattern Name)通过一两个词来描述模式的问题、解决方案和效果,以便更好地理解模式并方便开发人员之间的交流,绝大多数模式都是根据其功能或模式结构来命名的(GoF设计模式中没有一个模式用人名命名,微笑);问题(Problem)描述了应该在何时使用模式,它包含了设计中存在的问题以及问题存在的原因;解决方案(Solution)描述了一个设计模式的组成成分,以及这些组成成分之间的相互关系,各自的职责和协作方式,通常解决方案通过UML类图和核心代码来进行描述;效果(Consequences)描述了模式的优缺点以及在使用模式时应权衡的问题。
迪米特法则
迪米特法则-腾讯云开发者社区-腾讯云 (tencent.com)
迪米特法则,也称为最少知识原则(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) :需要适配的标准接口。
源对象(source) :需要被适配的不兼容对象。
适配器对象(adapter) :充当中间转换角色,该对象将源对象转换成目标接口。
模板方法
使用场景
当你有个通用的流程需要多个类共享,但某些步骤需要根据具体情况进行定义,可以考虑模板方法设计模式
与策略模式的差异
模板方法:侧重流程(框架)的共用,具体细节的替换
策略模式:侧重行为的多样性,关注算法的可替换性
样例演示
需要处理不同类型的数据文件(如文本文件、图片文件等)。我们可以定义一个抽象的基类 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)。售货机可以根据用户的操作改变状态,并相应地执行不同的行为。
观察者模式属于行为型模式 。在程序设计 中,观察者模式通常由两个对象组成:观察者和被观察者。当被观察者状态发生改变时,它会通知所有的观察者对象,使他们能够及时做出响应,所以也被称作“发布-订阅模式”
结构:
抽象被观察者:Newspaper
具体被观察者:NewspaperImpl
抽象观察者:Subscriber
具体观察者:SubscriberImpl