了解软件设计模式

设计模式有助于消除冗余编码。 了解如何使用 Java 中的单例模式、工厂模式和观察者模式。
164 位读者喜欢这篇文章。
What's new in OpenStack in 2016: A look at the Newton release

Opensource.com

如果您是程序员或正在攻读计算机科学或类似学科的学生,那么迟早您会遇到“软件设计模式”一词。 根据维基百科,软件设计模式是在软件设计中给定上下文中经常出现的问题的通用、可重用的解决方案。” 这是我对定义的理解:当您从事编码项目一段时间后,您经常开始思考,“嗯,这似乎是多余的。 我想知道我是否可以更改代码,使其更灵活并接受更改?” 因此,您开始思考如何将保持不变的部分与需要经常更改的部分分开。

设计模式是一种通过分离保持不变的部分和需要不断更改的部分来使您的代码更易于更改的方法。

毫不奇怪,每个从事编程项目的人可能都有过同样的想法。 特别是对于任何行业级别的项目,通常会与数十甚至数百名开发人员合作; 协作过程表明必须有一些标准和规则,以使代码更优雅并适应更改。 这就是我们拥有 面向对象编程 (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 语言的创建者在每次都创建一个新对象背后都有原因,但单例过程的支持者表示这是多余的并且浪费资源。

Normal class instantiation

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

Singleton pattern instantiation

以下脚本显示了创建单例模式的最简单方法

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();

此代码很好地演示了单例的想法

  1. 当调用 getInstance() 时,它会检查是否已通过检查空值来创建对象。
  2. 如果该值为空,它将创建一个新对象,将其保存到私有字段中,并将该对象返回给调用方。 否则,它将返回先前创建的对象。

此单例实现的主要问题在于它忽略了并行进程。 当使用线程的多个进程同时访问资源时,就会出现问题。 有一种解决方案可以解决这个问题,它被称为用于多线程安全的双重检查锁定,如下所示

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;
    }

}

为了强调前一点,请确保仅当您认为这样做是安全的选择时才直接实现单例。 最好的方法是通过使用制作精良的编程框架来利用单例功能。

工厂模式:将对象创建委托给工厂类以隐藏创建逻辑

工厂模式是另一种众所周知的设计模式,但它稍微复杂一些。 有几种实现工厂模式的方法,但以下示例代码演示了 最简单的方法。 工厂模式定义了用于创建对象的接口,但让子类决定要实例化哪个类。

工厂模式: 将对象创建委托给工厂类,以便它隐藏创建逻辑。

下图显示了如何实现最简单的工厂模式。

Factory pattern

客户端不是直接调用对象创建,而是向工厂类请求特定类型的对象 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";
    }
}

观察者模式:订阅主题并获取有关更新的通知

最后,还有观察者模式 与单例模式一样,很少有专业程序员直接实现观察者模式。 但是,许多消息队列和数据服务实现都借鉴了观察者模式的概念。 观察者模式定义了对象之间的一对多依赖关系,以便当一个对象更改状态时,将自动通知和更新其所有依赖项。

观察者模式: 订阅主题/主题,以便在有更新时通知客户端。

思考观察者模式的最简单方法是想象一个邮件列表,您可以在其中订阅任何主题,无论是开源、技术、名人、烹饪还是任何您感兴趣的其他内容。 每个主题都维护其订阅者的列表,这相当于观察者模式中的“观察者”。 当主题更新时,所有订阅者(观察者)都会收到更改通知。 订阅者始终可以取消订阅主题。

如下图所示,客户端可以订阅不同的主题,并添加观察者以接收有关新信息的通知。 因为观察者持续监听主题,所以观察者会在发生任何更改时通知客户端。

Observer pattern

让我们看一下观察者模式的示例代码,从主题/主题类开始

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");
    }
}

下一步做什么?

既然您已经阅读了本设计模式入门指南,那么您应该能够很好地学习其他设计模式,例如外观模式、模板模式和装饰器模式。 还有并发和分布式系统设计模式,例如断路器模式和 Actor 模式。

但是,我认为最好首先通过在您的副项目或仅作为练习中实现这些设计模式来磨练您的技能。 您甚至可以开始思考如何在您的实际项目中应用这些设计模式。 接下来,我强烈建议您查看 OOP 的 SOLID 原则。 之后,您就可以研究其他设计模式了。

User profile image.
Bryant Jimin Son 是 GitHub 的 Octocat(这不是官方头衔,但喜欢被这样称呼),GitHub 是一家以托管世界上大多数开源项目而闻名的公司。 在工作中,他正在探索不同的 git 技术、GitHub Actions、GitHub 安全性等。 此前,他曾担任 Red Hat 的高级顾问,Red Hat 是一家以其 Linux 服务器和开源贡献而闻名的技术公司。

4 条评论

在讽刺部分,作者不仅推荐了 IntelliJ IDEA,还使用了 URL https://www.jetbrains.com/idea/download/#section=mac。 虽然我对人们使用 Mac 没有任何个人问题,但我发现一个名为 OpenSource.com 的网站(由 Red Hat 运营)将其读者发送到专有 IDE 的 Mac 部分,这很搞笑。

你好,Erez。 您的反馈很有道理,但我也指出,用户可以选择使用 Eclipse IDE 等。

回复 作者:Erez Schatz

感谢您与我们分享您的经验

感谢这篇文章!

软件设计模式很有影响力,您的描述就证明了这一点。

通常,开发人员在编程过程中会犯错误,如果他们系统地遵循这些术语,我相信代码的质量会更好并且更容易访问。

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.