极客大学 - 架构师训练营 第三周

用户头像
9527
关注
发布于: 2020 年 10 月 03 日

第三周 代码重构



设计模式:使用设计模式优化排序工具包的设计

学习设计模式,学好设计模式,主要重点是去学习每种设计模式是解决什么问题的,它是如何解决这个问题的,并且在解决这个问题的过程中,体现了什么样的优势?

1. 什么是设计模式?

设计模式(Design Pattern)是一套被反复使用,大多数人知晓,经过分类编目的,代码设计经验的总结/方案。我们在平时开发和架构中使用设计模式,是为了让代码可以多重用,更容易被他人理解,并且保证代码的可靠性。在架构设计中使用合适的设计模式,对于自己,公司,他人都是共赢的。设计模式使代码编制真正的工程化,设计模式是软件工程的基石脉络。



一个设计模式通常分为四部分

2. 设计模式的分类
  • 功能

  • 创建模式 (Creational Patterns) - 类实例化过程的抽象

  • 结构模式 (Structural Patterns) - 类或者对象结合在一起形成更大的结构

  • 行为模式 (Behavioral Patterns) - 不同对象之间划分责任和算法的抽象化

  • 方式

  • 类模式 - 继承为主,静态

  • 对象模式 - 组合方式,动态

3. 为啥使用设计模式,好处大大的有

所谓模式,就是在某一背景下某个问题的一种解决方案。为啥张翠山能在扬刀大会上把武学造诣比他更高的金毛狮王比下去?就是用了“比写字”这种特别的解决方案。

  • 前人种树我乘凉:通过复用已经公认的设计,我们能够在解决问题时取得先发优势,而且避免重蹈前人覆辙。我可以从学习他人的经验中获益,用不着为那些总是会重复出现的问题再次设计解决方案了。

  • 说好普通话,方便你我他:开发中的交流和协作都需要共同的词汇基础和对问题的共识。设计模式在项目的分析和设计阶段提供了共同的基准点。

  • 掌握大局观:模式还为我们提供了观察问题、设计过程和面向对象的更高层次的视角,这将使我们从“过早处理细节”的桎梏中解放出来。上来撸起袖子就写代码,太low了

  • 久经沙场: 大多数设计模式还能使软件更容易修改和维护。其原因在于,它们都是久经考验的解决方案。所以,它们的结构都是经过长期发展形成的,比新构思的解决方案更善于应对变化。而且,这些模式所用代码往往更易于理解——从而使代码更易维护。

4. 设计模式在开发层级的位置



设计模式:Singleton单例模式

1. 灵魂问题
  • Singleton解决什么问题?

  • Singleton解决了减少实例频繁创建和销毁带来的资源消耗问题

  • 当多个用户使用同一个实例对象的时候,便于进行统一控制

  • Singleton的优点

  • 提供了唯一实例的受控访问,因为单例模式封装了唯一实例,所以可以严格控制客户怎样以及何时访问它

  • 由于系统中只存在一个资源,对于一些需要频繁创建和销毁的对象单例模式可以提高性能

  • 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例模式相似的方法来来获得指定个数的对象实例,既节省系统资源,又解决了单例对象共享过多有损性能的问题

  • Singleton的缺点

  • 由于单例模式没有抽象层,因此单例类的扩展有很大的困难

  • 单例类的职责过重,在一定程度上违背了‘单一职责的原则’

  • 什么场景下使用Singleton?

  • 需要统一管理资源,共享资源的时候需要使用

  • 场景示例

  • Windows的Task Manager,一台电脑只能打开一个任务管理器

  • 数据库的连接池,数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。

  • 多线程的线程池



设计模式:模版方法(Template Method)模式

1. 模版方法介绍

模版方法是扩展功能的最基本模式之一,是一种“类的行为模式”。该方法定义一个模板结构,将具体内容延迟到子类去实现。

2. 解决的问题
  • 提高代码复用性

  • 将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中

  • 实现了反向控制

  • 通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 & 符合“开闭原则”

  • 缺点

  • 引入了抽象类,每一个不同的实现都需要一个子类来实现,导致类的个数增加,从而增加了系统实现的复杂度

3. 如何实现扩展?

通过“继承”的方法来实现扩展

  • 基类负责算法的轮廓和骨架

  • 子类负责算法的具体实现



4. UML实现图


设计模式:策略模式(Strategy)

1. 为什么需要策略模式?

生活中完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。而在软件开发中也常常遇到类似的情况,实现某一个功能有多个途径,此时可以使用一种设计模式来使得系统可以灵活地选择解决途径,也能够方便地增加新的解决途径。因此我们可以定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法,在这里,每一个类我们都可以称之为策略(Strategy),为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体的实现则对应于一个具体策略类。

2. 策略模式定义

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。

策略模式是一种对象行为型模式。它有以下几个特点:

  • 策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。

  • 在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。

  • 策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。

3.策略模式结构
  • Context: 环境类

  • Strategy: 抽象策略类

  • ConcreteStrategy: 具体策略类

4. 策略模式时序图



6. 策略模式优缺点

优点:

  • 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。

  • 策略模式提供了管理相关的算法族的办法。

  • 策略模式提供了可以替换继承关系的办法。

  • 使用策略模式可以避免使用多重条件转移语句。

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。

  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。



设计模式:组合模式(Composite Pattern)

来,先看图,找一找熟悉的味道



上图是典型的文件结构,也称为树形结构。在数据结构和算法中,当我们对树进行遍历,然后到达叶子节点后,再进行某种操作。我们可以把树理解成一个大的容器,而容器里面包含很多的成员对象,这些成员对象可以是容器对象也可以是叶子对象。但是由于容器对象和叶子对象在功能上面的区别,使得我们在使用的过程中必须要区分容器对象和叶子对象,但是这样就会给客户带来不必要的麻烦,作为客户,它始终希望能够一致的对待容器对象和叶子对象。那么这就是组合设计模式要解决的问题:组合模式定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。

1. 组合模式定义

组合模式组合多个对象形成树形结构以表示"整体-部分"的结构层次。组合模式对单个对象和组合对象具有一致性,它将对象组织到树结构中,可以用来描述整体与部分的关系。同时它也模糊了单个对象和容器对象的概念,使得客户能够像处理简单元素一样来处理复杂元素,从而使客户程序能够与复杂元素的内部结构解耦。在使用组合模式中需要注意的是 (关键点): 定义了一个抽象构件类,它既可以代表叶子,又可以代表容器。叶子对象和组合对象实现相同的接口,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器。 这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。



2. 组合模式角色
  • Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。

  • Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。

  • Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。

3. 组合模式结构

父类 component 是一个抽象构建类, Folder 类是一个容器构建类, File 是一个叶子构建类。FolderFile 继承了 Component , FolderComponent 又是聚合关系。



透明组合模式

透明组合模式中,抽象构件角色中声明了所有用于管理成员对象的方法,譬如在示例中 Component 声明了 addremove 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。



透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)



安全组合模式

在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在容器构件 Composite 类中声明并实现这些方法。

安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。

4. 组合模式优缺点

优点:

  • 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易

  • 客户端调用简单,客户端可以一致的使用组合结构或其中的单个对象

  • 定义包含了叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,形成递归,组成更复杂的树形结构

  • 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构建而更改原有代码

缺点:

  • 使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联



5. 组合模式适用场景
  • 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们

  • 在一个使用面向对象语言开发的系统中需要处理一个树形结构

  • 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型



第三周 作业一

请在草稿纸上手写一个单例模式的实现代码,拍照提交作业

  • 函数版本

# Python Singleton decorator
import functools
def singleton(cls):
"""Make a class a Singleton class (Only one instance)"""
@functools.wraps(cls)
def wrapper_singleton(*args, **kwargs):
if not wrapper_singleton.instance:
wrapper_singleton.instance = cls(*args, **kwargs)
return wrapper_singleton.instance
wrapper_singleton.instance = None
return wrapper_singleton
# Test
@singleton
class Printer:
def __init__(self, name: str = 'hp101', ip: str = '192.168.0.3'):
self._name = name
self.ip = ip
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@property
def ip(self):
return self._ip
@ip.setter
def ip(self, value):
self._ip = value
if __name__ == '__main__':
printer_one = Printer()
printer_two = Printer()
print(f'id(printer_one) = {id(printer_one)}')
print(f'id(printer_two) = {id(printer_two)}')
  • 类版本

class Singleton:
"""
A non-thread-safe helper class to ease implementing singletons.
This should be used as a decorator -- not a metaclass -- to the
class that should be a singleton.
The decorated class can define one `__init__` function that
takes only the `self` argument. Also, the decorated class cannot be
inherited from. Other than that, there are no restrictions that apply
to the decorated class.
To get the singleton instance, use the `instance` method. Trying to
use `__call__` will result in a `TypeError` being raised.
"""
def __init__(self, decorated):
self._decorated = decorated
def instance(self):
"""
Returns the singleton instance. Upon its first call, it creates a new
instance of the decorated class and its `__init__` method.
On all subsequent calls, the already created instance is returned.
"""
try:
return self._instance
except AttributeError:
self._instance = self._decorated()
return self._instance
def __call__(self):
raise TypeError('Singletons must be accessed through `instance()`.')
def __instancecheck__(self, instance):
return isinstance(instance, self._decorated)
@Singleton
class Printer:
def __init__(self, name: str = 'hp101', ip: str = '192.168.0.3'):
self._name = name
self.ip = ip
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@property
def ip(self):
return self._ip
@ip.setter
def ip(self, value):
self._ip = value
if __name__ == '__main__':
printer_one = Printer.instance()
printer_two = Printer.instance()
print(f'id(printer_one) = {id(printer_one)}')
print(f'id(printer_two) = {id(printer_two)}')



为了让代码更具简洁性和高可用性,我创建了一个Python的装饰器,这个装饰器用来查看被装饰的类是否已经有了一个实例,有的话就返回已有实例,否则就创建一个新实例。

第三周 作业二

请用组合设计模式编写程序,打印输出图一的窗口,窗口组件的树结构如图2所示, 打印输出示例参考图3.

作业说明: https://github.com/tonylixu/architect-001/blob/master/week03/work02/README.md

作业链接: https://github.com/tonylixu/architect-001/tree/master/week03/work02

用户头像

9527

关注

还未添加个人签名 2020.04.22 加入

还未添加个人简介

评论

发布
暂无评论
极客大学 - 架构师训练营 第三周