如果您是一名程序员,或者是一名学习计算机科学或类似学科的学生,迟早您会遇到“软件设计模式”这个术语。 根据维基百科,*“软件设计模式是一种通用的、可重用的解决方案,用于解决在给定软件设计环境中经常出现的问题。”* 这是我对定义的理解:当您在一个编码项目上工作一段时间后,您经常会开始思考,“嗯,这似乎是多余的。我想知道是否可以更改代码,使其更灵活并更容易接受更改?”因此,您开始考虑如何将保持不变的部分与需要经常更改的部分分开。
一个 设计模式 是一种通过分离保持不变的部分和需要经常更改的部分来使代码更易于更改的方法。
毫不奇怪,每个从事编程项目的人可能都有过相同的想法。 特别是对于任何行业级别的项目,通常与几十甚至数百名开发人员合作; 协作过程表明必须有一些标准和规则,使代码更加优雅并适应更改。 这就是我们拥有 面向对象编程 (OOP) 和 软件框架工具的原因。 设计模式在某种程度上类似于 OOP,但它更进一步,将更改视为自然开发过程的一部分。 基本上,设计模式利用了 OOP 的一些思想,例如抽象和接口,但侧重于更改的过程。
当您开始一个项目时,您经常会听到术语 *重构*,这意味着 *更改代码以使其更优雅和可重用*; 这就是设计模式的闪光点。 无论您正在处理现有代码(由其他人或您过去的自己构建),了解设计模式都可以帮助您开始以不同的方式看待事物——您会发现问题并找到改进代码的方法。
设计模式有很多种,但我在本入门文章中介绍的三种流行的设计模式是单例模式、工厂模式和观察者模式。
如何学习本指南
我希望本教程对于任何人来说都尽可能简单易懂,无论您是经验丰富的程序员还是编码初学者。 设计模式的概念并不是很容易理解,并且在您开始一段旅程时降低学习曲线始终是重中之重。 因此,除了这篇包含图表和代码片段的文章之外,我还创建了一个 GitHub 存储库,您可以克隆并运行该代码以自行实现这三种设计模式。 您还可以按照我创建的以下 YouTube 视频进行学习。
先决条件
如果您只想了解设计模式的一般概念,则无需克隆示例项目或安装任何工具。 但是,要运行示例代码,您需要安装以下内容
- Java 开发工具包 (JDK): 我强烈推荐 OpenJDK。
- Apache Maven: 示例项目是使用 Apache Maven 构建的; 幸运的是,许多 IDE 都安装了 Maven。
- 交互式开发编辑器 (IDE): 我使用 IntelliJ Community Edition,但您可以使用 Eclipse IDE 或您选择的任何其他 Java IDE
- Git: 如果您想克隆项目,则需要一个 Git 客户端。
要克隆项目并继续操作,请在安装 Git 后运行以下命令
git clone https://github.com/bryantson/OpensourceDotComDemos.git
然后,在您喜欢的 IDE 中,您可以将 TopDesignPatterns 存储库中的代码作为 Apache Maven 项目导入。
我正在使用 Java,但您可以使用任何支持 抽象原则的编程语言来实现设计模式。
单例模式:避免每次都创建一个对象
单例模式是一种非常流行的设计模式,它也相对容易实现,因为您只需要一个类。 然而,许多开发人员争论单例设计模式的优势是否超过了其问题,因为它缺乏明显的优势并且容易被滥用。 很少有开发人员直接实现单例; 相反,Spring Framework 和 Google Guice 等编程框架具有内置的单例设计模式功能。
但是了解单例仍然非常有用。 单例模式确保一个类只被创建一次,并提供对它的全局访问点。
单例模式: 确保只创建一个实例,并避免创建同一个对象的多个实例。
下图显示了创建类对象的典型过程。 当客户端请求创建一个对象时,构造函数会创建一个对象,或者说实例化一个对象,然后返回给带有调用方法的类。 然而,每次请求一个对象时都会发生这种情况——构造函数被调用,一个新对象被创建,然后它返回一个唯一的对象。 我想 OOP 语言的创建者每次都创建一个新对象是有原因的,但是单例过程的支持者说这是多余的并且浪费资源。

下图使用单例模式创建对象。 在这里,只有当第一次通过指定的 getInstance() 方法请求对象时才调用构造函数。 这通常通过检查空值来完成,并且该对象作为私有字段值保存在单例类中。 下一次调用 getInstance() 时,该类将返回第一次创建的对象。 不会创建新对象; 它只是返回旧的。

以下脚本显示了创建单例模式的最简单方法
package org.opensource.demo.singleton;
public class OpensourceSingleton {
private static OpensourceSingleton uniqueInstance;
private OpensourceSingleton() {
}
public static OpensourceSingleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new OpensourceSingleton();
}
return uniqueInstance;
}
}
在调用方,以下是如何调用单例类来获取对象
Opensource newObject = Opensource.getInstance();
此代码很好地演示了单例的概念
- 当调用 getInstance() 时,它通过检查空值来检查是否已经创建了对象。
- 如果值为 null,则它创建一个新对象,将其保存到私有字段中,然后将该对象返回给调用方。 否则,它返回先前创建的对象。
此单例实现的主要问题在于它忽略了并行进程。 当使用线程的多个进程同时访问资源时,就会出现问题。 有一个解决方案,称为用于多线程安全的双重检查锁定,如下所示
package org.opensource.demo.singleton;
public class ImprovedOpensourceSingleton {
private volatile static ImprovedOpensourceSingleton uniqueInstance;
private ImprovedOpensourceSingleton() {}
public static ImprovedOpensourceSingleton getInstance() {
if (uniqueInstance == null) {
synchronized (ImprovedOpensourceSingleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new ImprovedOpensourceSingleton();
}
}
}
return uniqueInstance;
}
}
只是为了强调前一点,请确保仅在您认为它是安全选项时才直接实现您的单例。 最好的方法是使用制作精良的编程框架来利用单例功能。
工厂模式:将对象创建委托给工厂类以隐藏创建逻辑
工厂模式是另一种众所周知的设计模式,但它稍微复杂一些。 有几种实现工厂模式的方法,但以下示例代码演示了最简单的方法。 工厂模式定义了创建对象的接口,但让子类决定实例化哪个类。
工厂模式: 将对象创建委托给工厂类,因此它隐藏了创建逻辑。
下图显示了如何实现最简单的工厂模式。

客户端不是直接调用对象创建,而是向工厂类请求某个对象,类型 x。根据该类型,工厂模式决定创建哪个对象并返回。
在此代码示例中,OpensourceFactory 是工厂类实现,它从调用方获取类型并根据该输入值决定创建哪个对象
package org.opensource.demo.factory;
public class OpensourceFactory {
public OpensourceJVMServers getServerByVendor(String name) {
if(name.equals("Apache")) {
return new Tomcat();
}
else if(name.equals("Eclipse")) {
return new Jetty();
}
else if (name.equals("RedHat")) {
return new WildFly();
}
else {
return null;
}
}
}
OpenSourceJVMServer 是一个 100% 抽象类(或接口类),指示要实现什么,而不是如何实现
package org.opensource.demo.factory;
public interface OpensourceJVMServers {
public void startServer();
public void stopServer();
public String getName();
}
这是一个 OpensourceJVMServers 的示例实现类。 当“RedHat”作为类型传递给工厂类时,将创建 WildFly 服务器
package org.opensource.demo.factory;
public class WildFly implements OpensourceJVMServers {
public void startServer() {
System.out.println("Starting WildFly Server...");
}
public void stopServer() {
System.out.println("Shutting Down WildFly Server...");
}
public String getName() {
return "WildFly";
}
}
观察者模式:订阅主题并获得更新通知
最后,还有观察者模式。 与单例模式一样,很少有专业的程序员直接实现观察者模式。 然而,许多消息队列和数据服务实现都借鉴了观察者模式的概念。 观察者模式定义了对象之间的一对多依赖关系,以便当一个对象更改状态时,其所有依赖项都会自动收到通知并更新。
观察者模式: 订阅主题/主题,如果存在更新,可以通知客户端。
思考观察者模式的最简单方法是想象一个邮件列表,您可以在其中订阅任何主题,无论是开源、技术、名人、烹饪还是任何您感兴趣的其他内容。 每个主题都维护其订阅者列表,这相当于观察者模式中的“观察者”。 当主题更新时,所有订阅者(观察者)都会收到更改通知。 订阅者始终可以取消订阅主题。
如下图所示,客户端可以订阅不同的主题,并添加观察者以接收有关新信息的通知。 因为观察者持续监听主题,所以观察者会将发生的任何更改通知客户端。

让我们看一下观察者模式的示例代码,从主题/主题类开始
package org.opensource.demo.observer;
public interface Topic {
public void addObserver(Observer observer);
public void deleteObserver(Observer observer);
public void notifyObservers();
}
此代码描述了一个接口,用于不同的主题来实现定义的方法。 请注意,如何添加、删除或通知观察者。
这是一个主题的示例实现
package org.opensource.demo.observer;
import java.util.List;
import java.util.ArrayList;
public class Conference implements Topic {
private List<Observer> listObservers;
private int totalAttendees;
private int totalSpeakers;
private String nameEvent;
public Conference() {
listObservers = new ArrayList<Observer>();
}
public void addObserver(Observer observer) {
listObservers.add(observer);
}
public void deleteObserver(Observer observer) {
int i = listObservers.indexOf(observer);
if (i >= 0) {
listObservers.remove(i);
}
}
public void notifyObservers() {
for (int i=0, nObservers = listObservers.size(); i < nObservers; ++ i) {
Observer observer = listObservers.get(i);
observer.update(totalAttendees,totalSpeakers,nameEvent);
}
}
public void setConferenceDetails(int totalAttendees, int totalSpeakers, String nameEvent) {
this.totalAttendees = totalAttendees;
this.totalSpeakers = totalSpeakers;
this.nameEvent = nameEvent;
notifyObservers();
}
}
此类定义了特定主题的实现。 当发生更改时,将在此实现中调用它。 请注意,这采用了观察者的数量,该数量存储为列表,并且可以通知和维护观察者。
这是一个观察者类
package org.opensource.demo.observer;
public interface Observer {
public void update(int totalAttendees, int totalSpeakers, String nameEvent);
}
这个类定义了一个接口,不同的观察者可以实现该接口来执行特定的操作。
例如,观察者实现可以打印出会议的与会者和演讲者的数量
package org.opensource.demo.observer;
public class MonitorConferenceAttendees implements Observer {
private int totalAttendees;
private int totalSpeakers;
private String nameEvent;
private Topic topic;
public MonitorConferenceAttendees(Topic topic) {
this.topic = topic;
topic.addObserver(this);
}
public void update(int totalAttendees, int totalSpeakers, String nameEvent) {
this.totalAttendees = totalAttendees;
this.totalSpeakers = totalSpeakers;
this.nameEvent = nameEvent;
printConferenceInfo();
}
public void printConferenceInfo() {
System.out.println(this.nameEvent + " has " + totalSpeakers + " speakers and " + totalAttendees + " attendees");
}
}
下一步做什么?
现在您已经阅读了这篇设计模式的介绍性指南,您应该可以很好地学习其他设计模式,例如外观模式(facade)、模板模式(template)和装饰器模式(decorator)。还有并发和分布式系统设计模式,例如断路器模式(circuit breaker pattern)和 Actor 模式。
但是,我认为最好的方法是首先通过在您的业余项目或仅仅作为练习中实现这些设计模式来磨练您的技能。您甚至可以开始思考如何在您的实际项目中应用这些设计模式。接下来,我强烈建议您查看 OOP 的 SOLID 原则。之后,您就可以开始研究其他设计模式了。
4 条评论