设计模式
设计模式,即Design Patterns,是指在软件设计中,被反复使用的一种代码设计经验。 使用设计模式的目的是为了可重用代码,提高代码的可扩展性和可维护性
一:总纲
按照王老师的体系,23种设计模式,看上去很多,但万变不离其宗.一切设计模式,均是离不开这两张图.
具体来说,就如下两种:
1. 只有继承关系的:
DIP:依赖倒转原则,细节应当依赖于抽象,抽象不应当依赖于细节。
OCP:开闭原则,软件实体应当对扩展开放,对修改关闭.
特点:
只有一颗继承树
模板方法:比如,我写了一个白盒框架,现在要往里面加东西,我希望用户来进行手动添加,那么,我给用户一个接口,下面的子类型实现是可以进行任意安排的.
通过另一组继承树,对内在的实现进行了隐藏,具体的体现比如说:Factory方法,Adapter方法,装饰器模式,等等.
对于装饰器来说,可以认为这两颗树均是自己,通过自己对自己进行委托,进行功能的附加.
下面,我们来具体的对王老师上课所授的7种设计模式展开具体分析:
二:具体的7种设计模式
2.1 工厂方法:
工厂方法是一种创建型设计模式,它将对象的创建委托给子类。它定义了一个用于创建对象的接口,但让子类决定实例化哪个类。这样可以将一个类的实例化延迟到其子类中。工厂方法提供了一种灵活的方式来创建对象,同时也遵循了开放封闭原则,即对扩展开放,对修改封闭。
以下给出我利用工厂方法创建不同类型的日志记录器:
// 定义日志记录器接口
public interface Logger {
void log(String message);
}
// 创建文件日志记录器
public class FileLogger implements Logger {
public void log(String message) {
System.out.println("Logging message to file: " + message);
}
}
// 创建数据库日志记录器
public class DatabaseLogger implements Logger {
public void log(String message) {
System.out.println("Logging message to database: " + message);
}
}
// 创建日志记录器工厂
public class LoggerFactory {
public Logger getLogger(String loggerType) {
if (loggerType.equalsIgnoreCase("file")) {
return new FileLogger();
} else if (loggerType.equalsIgnoreCase("database")) {
return new DatabaseLogger();
} else {
throw new IllegalArgumentException("Invalid logger type");
}
}
}
// 使用日志记录器工厂创建并使用日志记录器
public class Main {
public static void main(String[] args) {
LoggerFactory factory = new LoggerFactory();
Logger logger = factory.getLogger("file");
logger.log("This is a log message");
}
}
2.2 适配器模式
考虑下列情形:
假如我们在进行一个机器学习的项目,我们的核心算法,它需要提供一个numpy形式的矩阵,而我们的数据采集器,返回的矩阵,是一个excel文件.
这个时候,我们就需要一个适配器,帮我们干活了
它通过委托与接口,在二者之间建立了如下的桥梁;
2.3 装饰器模式
人靠衣装,在街上走,你会看到有穿T恤的,穿长袖夹克的,有穿背心的各种各样的人.
那么,如果要建立这样的关系的话,我们是可以通过继承来组合,也可以通过建立装饰器来进行委托.
如果是组合的话,问题会越来越麻烦.
设想,假如一开始所有人都穿T恤,那么到后来,秋天了,大家穿长袖的,有可能是里面套了一个背心,也有可能是里面传了一件毛衣,那么我们都用继承来进行处理的话,这个组合树,将会越来越大.问题很大!
这就是组合爆炸
所以,正确的做法是,让类学会穿衣服.不同的衣服,展现出不同的特性.有不同的功能
以下是我使用装饰器模式来动态地添加功能:
// 定义组件接口
public interface Component {
void operation();
}
// 创建具体组件
public class ConcreteComponent implements Component {
public void operation() {
System.out.println("ConcreteComponent operation.");
}
}
// 创建装饰器基类
public abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}
// 创建具体装饰器
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
public void operation() {
super.operation();
System.out.println("ConcreteDecoratorA operation.");
}
}
// 创建另一个具体装饰器
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
public void operation() {
super.operation();
System.out.println("ConcreteDecoratorB operation.");
addedBehavior();
}
private void addedBehavior() {
System.out.println("Added behavior in ConcreteDecoratorB.");
}
}
// 使用装饰器动态地添加功能
public class Main {
public static void main(String[] args) {
Component component = new ConcreteComponent();
component.operation();
System.out.println();
Component decoratedComponentA = new ConcreteDecoratorA(component);
decoratedComponentA.operation();
System.out.println();
Component decoratedComponentB = new ConcreteDecoratorB(decoratedComponentA);
decoratedComponentB.operation();
}
}
在上面的示例中,我们定义了一个Component
接口来表示组件,和一个具体的实现ConcreteComponent
。然后,我们创建了一个抽象的装饰器类Decorator
,它维护了一个指向组件的引用,并实现了Component
接口。接着,我们创建了两个具体的装饰器类ConcreteDecoratorA
和ConcreteDecoratorB
,它们都扩展了Decorator
类,并在原有功能的基础上添加了新的行为。
在Main
类中,我们首先创建了一个ConcreteComponent
对象,并调用了它的operation()
方法。然后,我们创建了一个ConcreteDecoratorA
对象,并将ConcreteComponent
对象传递给它进行装饰。最后,我们创建了一个ConcreteDecoratorB
对象,并将ConcreteDecoratorA
对象传递给它进行装饰。这样,我们就可以动态地添加多个装饰器来扩展组件的功能。
2.4策略模式
考虑如下的场景:
假如我们在写一个文本翻译的APP,用户输入文本,我们对其进行翻译.
我们一开始是采用SVD分解对文本进行词嵌入.后来,竞争对手纷纷采用了BERT来进行词嵌入,效果比我们好很多.那么,我们也想要用BERT来进行,不过由于考虑到以后的扩展,我们决定将词嵌入,放到一个方法类里面去进行.
即这里的策略类.
策略模式:
存在多种算法来处理同一个任务,但client需要根据需要动态切换算法.
我们可以为不同的实现算法构造抽象接口.利用委托,在运行时,动态传入Client倾向的算法类的实例.
2.5 模板模式
模板模式是一种行为型设计模式,它定义了一个算法的骨架,将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。这种模式遵循了开放封闭原则,即对扩展开放,对修改封闭。
模板模式通常由一个抽象类和若干个具体子类组成,其中抽象类定义了算法的骨架,具体子类实现了算法中的具体步骤。在模板方法中,抽象类会定义一系列的抽象方法或受保护方法,这些方法将在具体子类中得到实现。
模板模式的优点是可以提高代码的复用性和可维护性,同时也可以使得算法的实现更加灵活和可扩展。缺点是增加了代码的抽象性,可能会使得代码更加难以理解和调试。
2.6 迭代器模式
用途:我们给外界用户的往往是一个黑箱,如果用户有想要逛逛的需求的话,我们便要在既维护内在表示的基础上,又满足外界用户的需求.
在Java里面已经提供了下面的接口:
Iterable接口:实现这个的集合对象是可遍历的
Iterator接口:迭代器,实现这个的是可以进行显示/隐式的进行迭代的.
以下是我使用迭代器模式来遍历一个集合:
// 定义迭代器接口
public interface Iterator {
boolean hasNext();
Object next();
}
// 创建具体迭代器
public class ConcreteIterator implements Iterator {
private List<Object> list;
private int index = 0;
public ConcreteIterator(List<Object> list) {
this.list = list;
}
public boolean hasNext() {
return index < list.size();
}
public Object next() {
if (hasNext()) {
return list.get(index++);
}
return null;
}
}
// 创建集合接口
public interface Aggregate {
Iterator createIterator();
}
// 创建具体集合
public class ConcreteAggregate implements Aggregate {
private List<Object> list = new ArrayList<>();
public void add(Object obj) {
list.add(obj);
}
public void remove(Object obj) {
list.remove(obj);
}
public Iterator createIterator() {
return new ConcreteIterator(list);
}
}
// 使用迭代器遍历集合
public class Main {
public static void main(String[] args) {
Aggregate aggregate = new ConcreteAggregate();
aggregate.add("A");
aggregate.add("B");
aggregate.add("C");
Iterator iterator = aggregate.createIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
在上面的示例中,我们定义了一个Iterator
接口来表示迭代器,和一个具体的实现ConcreteIterator
。然后,我们创建了一个Aggregate
接口来表示集合,和一个具体的实现ConcreteAggregate
。在ConcreteAggregate
中,我们使用一个List
来存储集合元素,并实现了createIterator()
方法来返回一个ConcreteIterator
对象。最后,在Main
类中,我们创建了一个ConcreteAggregate
对象,并向其中添加了三个元素。然后,我们创建了一个迭代器对象,并使用它来遍历集合中的元素。
迭代器模式的优点是可以提供一种统一的遍历集合元素的方式,同时也可以隐藏集合的内部实现细节。缺点是需要额外的迭代器类,可能会增加代码的复杂性。
2.7 Visitor模式
简单点说:就是预留扩展点,以便日后的扩展
为我们的对象的特定Visit,运行时进行动态的绑定.操作可以灵活更改的.方便以后操作.