您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

理解软件设计模式

时间:2019-09-03 15:17:59  来源:  作者:
理解软件设计模式

 

设计模式可以帮助消除冗余代码。学习如何利用 JAVA 使用单例模式、工厂模式和观察者模式。

-- Bryant Son(作者)

如果你是一名正在致力于计算机科学或者相关学科的程序员或者学生,很快,你将会遇到一条术语 “ 软件设计模式(software design pattern)”。根据维基百科,“ 软件设计模式 是在平常的软件设计工作中所遭遇的问题的一种通用的、可重复使用的解决方案”。我对该定义的理解是:当在从事于一个编码项目时,你经常会思考,“嗯,这里貌似是冗余代码,我觉得是否能改变这些代码使之更灵活和便于修改?”因此,你会开始考虑怎样分割那些保持不变的内容和需要经常改变的内容。

设计模式是一种通过分割那些保持不变的部分和经常变化的部分,让你的代码更容易修改的方法。

不出意外的话,每个从事编程项目的人都可能会有同样的思考。特别是那些工业级别的项目,在那里通常工作着数十甚至数百名开发者;协作过程表明必须有一些标准和规则来使代码更加优雅并适应变化。这就是为什么我们有了 面向对象编程 (OOP)和 软件框架工具 。设计模式有点类似于 OOP,但它通过将变化视为自然开发过程的一部分而进一步发展。基本上,设计模式利用了一些 OOP 的思想,比如抽象和接口,但是专注于改变的过程。

当你开始开发项目时,你经常会听到这样一个术语重构,它意味着通过改变代码使它变得更优雅和可复用;这就是设计模式耀眼的地方。当你处理现有代码时(无论是由其他人构建还是你自己过去构建的),了解设计模式可以帮助你以不同的方式看待事物,你将发现问题以及改进代码的方法。

有很多种设计模式,其中单例模式、工厂模式和观察者模式三种最受欢迎,在这篇文章中我将会一一介绍它们。

如何遵循本指南

无论你是一位有经验的编程工作者还是一名刚刚接触的新手,我想让这篇教程让每个人都很容易理解。设计模式概念并不容易理解,减少开始旅程时的学习曲线始终是首要任务。因此,除了这篇带有图表和代码片段的文章外,我还创建了一个 GitHub 仓库 ,你可以克隆仓库并在你的电脑上运行这些代码来实现这三种设计模式。你也可以观看我创建的 YouTube视频 。

必要条件

如果你只是想了解一般的设计模式思想,则无需克隆示例项目或安装任何工具。但是,如果要运行示例代码,你需要安装以下工具:

  • Java 开发套件(JDK):我强烈建议使用 OpenJDK 。
  • Apache Maven:这个简单的项目使用 Apache Maven 构建;好的是许多 IDE 自带了Maven。
  • 交互式开发编辑器(IDE):我使用 社区版 IntelliJ ,但是你也可以使用 Eclipse IDE 或者其他你喜欢的 Java IDE。
  • Git:如果你想克隆这个工程,你需要 Git 客户端。

安装好 Git 后运行下列命令克隆这个工程:

git clone https://github.com/bryantson/OpensourceDotComDemos.git

然后在你喜欢的 IDE 中,你可以将 TopDesignPatterns 仓库中的代码作为 Apache Maven 项目导入。

我使用的是 Java,但你也可以使用支持 抽象原则 的任何编程语言来实现设计模式。

单例模式:避免每次创建一个对象

单例模式 (singleton pattern)是非常流行的设计模式,它的实现相对来说很简单,因为你只需要一个类。然而,许多开发人员争论单例设计模式的是否利大于弊,因为它缺乏明显的好处并且容易被滥用。很少有开发人员直接实现单例;相反,像 Spring Framework 和 google Guice 等编程框架内置了单例设计模式的特性。

但是了解单例模式仍然有巨大的用处。单例模式确保一个类仅创建一次且提供了一个对它的全局访问点。

单例模式:确保仅创建一个实例且避免在同一个项目中创建多个实例。

下面这幅图展示了典型的类对象创建过程。当客户端请求创建一个对象时,构造函数会创建或者实例化一个对象并调用方法返回这个类给调用者。但是每次请求一个对象都会发生这样的情况:构造函数被调用,一个新的对象被创建并且它返回了一个独一无二的对象。我猜面向对象语言的创建者有每次都创建一个新对象的理由,但是单例过程的支持者说这是冗余的且浪费资源。

 

理解软件设计模式

Normal class instantiation

 

下面这幅图使用单例模式创建对象。这里,构造函数仅当对象首次通过调用预先设计好的 getInstance() 方法时才会被调用。这通常通过检查该值是否为 null 来完成,并且这个对象被作为私有变量保存在单例类的内部。下次 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() 被调用时,它通过检查 null 值来检查对象是否已经被创建。
  2. 如果值为 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;
 }
}

再强调一下前面的观点,确保只有在你认为这是一个安全的选择时才直接实现你的单例模式。最好的方法是通过使用一个制作精良的编程框架来利用单例功能。

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

工厂模式 (factory pattern)是另一种众所周知的设计模式,但是有一小点复杂。实现工厂模式的方法有很多,而下列的代码示例为最简单的实现方式。为了创建对象,工厂模式定义了一个接口,让它的子类去决定实例化哪一个类。

工厂模式:将对象创建委派给工厂类,因此它能隐藏创建逻辑。

下列的图片展示了最简单的工厂模式是如何实现的。

 

理解软件设计模式

Factory pattern

 

客户端请求工厂类创建类型为 x 的某个对象,而不是客户端直接调用对象创建。根据其类型,工厂模式决定要创建和返回的对象。

在下列代码示例中,OpensourceFactory 是工厂类实现,它从调用者那里获取类型并根据该输入值决定要创建和返回的对象:

package org.opensource.demo.factory;
public class OpensourceFactory {
 public OpensourceJVMServers getServerByVendor([String][18] 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][18] getName();
}

这是一个 OpensourceJVMServers 类的实现示例。当 RedHat 被作为类型传递给工厂类,WildFly 服务器将被创建:

package org.opensource.demo.factory;
public class WildFly implements OpensourceJVMServers {
 public void startServer() {
 [System][19].out.println("Starting WildFly Server...");
 }
 public void stopServer() {
 [System][19].out.println("Shutting Down WildFly Server...");
 }
 public [String][18] getName() {
 return "WildFly";
 }
}

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

最后是 观察者模式 (observer pattern)。像单例模式那样,很少有专业的程序员直接实现观察者模式。但是,许多消息队列和数据服务实现都借用了观察者模式的概念。观察者模式在对象之间定义了一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将被自动地通知和更新。

观察者模式:如果有更新,那么订阅了该话题/主题的客户端将被通知。

理解观察者模式的最简单方法是想象一个邮件列表,你可以在其中订阅任何主题,无论是开源、技术、名人、烹饪还是您感兴趣的任何其他内容。每个主题维护者一个它的订阅者列表,在观察者模式中它们相当于观察者。当某一个主题更新时,它所有的订阅者(观察者)都将被通知这次改变。并且订阅者总是能取消某一个主题的订阅。

如下图所示,客户端可以订阅不同的主题并添加观察者以获得最新信息的通知。因为观察者不断的监听着这个主题,这个观察者会通知客户端任何发生的改变。

 

理解软件设计模式

Observer pattern

 

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

package org.opensource.demo.observer;
public interface Topic {
 public void addObserver([Observer][22] observer);
 public void deleteObserver([Observer][22] 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][18] nameEvent;
 public Conference() {
 listObservers = new ArrayList<Observer>();
 }
 public void addObserver([Observer][22] observer) {
 listObservers.add(observer);
 }
 public void deleteObserver([Observer][22] 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][22] observer = listObservers.get(i);
 observer.update(totalAttendees,totalSpeakers,nameEvent);
 }
 }
 public void setConferenceDetails(int totalAttendees, int totalSpeakers, [String][18] nameEvent) {
 this.totalAttendees = totalAttendees;
 this.totalSpeakers = totalSpeakers;
 this.nameEvent = nameEvent;
 notifyObservers();
 }
}

这段代码定义了一个特定主题的实现。当发生改变时,这个实现调用它自己的方法。注意这将获取观察者的数量,它以列表方式存储,并且可以通知和维护观察者。

这是一个观察者类:

package org.opensource.demo.observer;
public interface [Observer][22] {
 public void update(int totalAttendees, int totalSpeakers, [String][18] nameEvent);
}

这个类定义了一个接口,不同的观察者可以实现该接口以执行特定的操作。

例如,实现了该接口的观察者可以在会议上打印出与会者和发言人的数量:

package org.opensource.demo.observer;
public class MonitorConferenceAttendees implements [Observer][22] {
 private int totalAttendees;
 private int totalSpeakers;
 private [String][18] nameEvent;
 private Topic topic;
 public MonitorConferenceAttendees(Topic topic) {
 this.topic = topic;
 topic.addObserver(this);
 }
 public void update(int totalAttendees, int totalSpeakers, [String][18] nameEvent) {
 this.totalAttendees = totalAttendees;
 this.totalSpeakers = totalSpeakers;
 this.nameEvent = nameEvent;
 printConferenceInfo();
 }
 public void printConferenceInfo() {
 [System][19].out.println(this.nameEvent + " has " + totalSpeakers + " speakers and " + totalAttendees + " attendees");
 }
}

接下来

现在你已经阅读了这篇对于设计模式的介绍引导,你还可以去寻求了解其他设计模式,例如外观模式,模版模式和装饰器模式。也有一些并发和分布式系统的设计模式如断路器模式和锚定模式。

可是,我相信最好的磨砺你的技能的方式首先是通过在你的业余项目或者练习中实现这些设计模式。你甚至可以开始考虑如何在实际项目中应用这些设计模式。接下来,我强烈建议你查看 OOP 的 SOLID 原则 。之后,你就准备好了解其他设计模式。


via: https://opensource.com/article/19/7/understanding-software-design-patterns

作者: Bryant Son 选题: lujun9972 译者: arrowfeng 校对: wxy

本文由 LCTT 原创编译, linux中国 荣誉推出



Tags:软件设计   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
1.开闭原则⼀个软件实体,如类,模块和函数应该对扩展开放,对修改关闭。所谓的开闭,是对扩展和修改两个⾏为的⼀个原则。需要强调的是利⽤抽象构建框架,⽤实现扩展细节。开闭原则是...【详细内容】
2021-03-01  Tags: 软件设计  点击:(237)  评论:(0)  加入收藏
电量:对于移动设备最大的瓶颈就是电量了。因为用户不可能随时携带电源,充电宝。所以必须考虑到电量问题。那就要检查我们工程是不是有后台运行,心跳包发送时间是不是合理。流量...【详细内容】
2019-11-13  Tags: 软件设计  点击:(99)  评论:(0)  加入收藏
电量:对于移动设备最大的瓶颈就是电量了。因为用户不可能随时携带电源,充电宝。所以必须考虑到电量问题。那就要检查我们工程是不是有后台运行,心跳包发送时间是不是合理。流量...【详细内容】
2019-11-13  Tags: 软件设计  点击:(88)  评论:(0)  加入收藏
设计模式可以帮助消除冗余代码。学习如何利用 Java 使用单例模式、工厂模式和观察者模式。-- Bryant Son(作者)如果你是一名正在致力于计算机科学或者相关学科的程序员或者学...【详细内容】
2019-09-03  Tags: 软件设计  点击:(171)  评论:(0)  加入收藏
▌简易百科推荐
本文分为三个等级自顶向下地分析了glibc中内存分配与回收的过程。本文不过度关注细节,因此只是分别从arena层次、bin层次、chunk层次进行图解,而不涉及有关指针的具体操作。前...【详细内容】
2021-12-28  linux技术栈    Tags:glibc   点击:(3)  评论:(0)  加入收藏
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(2)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(10)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(20)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(25)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(25)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条