设计模式介绍
起源于 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 《设计模式 - 可复用的面向对象软件元素》 的书,该书首次提到了软件开发中设计模式的概念。 设计模式是面向对象软件开发解决一般问题的最佳实践,这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。合理使用设计模式能使我们的代码结构良好,易于理解,易于维护和扩展。
4种设计原则
开放封闭原则 对扩展开放,对修改关闭。简单理解就是面对需求,对程序的改动是通过增加新的代码,而不是更改现有的代码。 里氏代换原则 继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。 依赖倒转原则 针对接口编程,依赖于抽象而不依赖于具体,降低类与类之间的耦合。 单一职责原则 一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
23种常用的设计模式
单例模式
特点:保证一个类只有一个实例,并提供它的一个全局访问点。 使用场景:想控制实例数目、节省系统资源的时候,避免一个全局使用的类频繁地创建与销毁。
- Windows的任务管理器,只能打开一个
- 数据库的连接池、多线程的线程池等
- 全局配置文件读取对象
ruby中单例的实现
require 'singleton'
class Demo
include Singleton
def test
end
end
obj1 = Demo.instance
obj2 = Demo.instance
p obj1,obj2
# 输出
#<Demo:0x00007f95b5727f70>
#<Demo:0x00007f95b5727f70>
简单工厂模式
特点:据传入参数的不同来创建不同的实例,被创建的实例通常具有相同的父类。 使用场景:需要生成复杂对象的地方,需要根据不同的条件生成不同的对象,当类之间需要增加依赖关系时,可以使用工厂模式降低系统之间的耦合度。
- 日志记录器:日志可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方
- 数据库访问:当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时
ruby中简单工厂的实现
# 例子:实现一个计算器程序,可以灵活的扩展运算符种类
#运算类
class Operation
attr_accessor :number_a,:number_b
def initialize(number_a, number_b)
@number_a = number_a
@number_b = number_b
end
def result; end
end
#加法类
class OperationAdd < Operation
def result
number_a + number_b
end
end
#减法类
class OperationSub < Operation
def result
number_a - number_b
end
end
# 工厂类
class Factory
def self.create_operate(opt)
case opt
when '+'
OperationAdd.new
when '-'
OperationSub.new
end
end
end
opt = Factory.create_operate('+')
opt.number_a = 3
opt.number_b = 2
p opt.result
抽象工厂模式
特点:在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。 使用场景:扩展简单工厂模式无法适应多类别产品的问题
- QQ 换皮肤,一整套一起换。
- 生成不同操作系统的程序。
ruby中抽象工厂的实现
# 运算类
class Operation
attr_accessor :number_a,:number_b
def initialize(number_a = nil, number_b = nil)
@number_a = number_a
@number_b = number_b
end
def result
end
end
# 加法类
class OperationAdd < Operation
def result
number_a + number_b
end
end
# 减法类
class OperationSub < Operation
def result
number_a - number_b
end
end
# 工厂接口
module OperationFactory
def create_operate; end
end
# 加法类的工厂
class OperationAdd < Operation
include OperationFactory
def create_operate
OperationAdd.new
end
end
# 减法类工厂
class OperationSub < Operation
include OperationFactory
def create_operate
OperationSub.new
end
end
# 创建加法类的工厂
factory = OperationAdd.new
# 通过加法类的工厂来生产加法类
add_opt = factory.create_operate
add_opt.number_a = 3
add_opt.number_b = 3
p add_opt.result
# 运算符类相当于产品,工厂类相当于流水线,当开发一块新产品,只需要新开一条产品线,
# 不需要修改原来的产品线,符合开放封闭原则
建造者模式
特点:使用多个简单的对象一步一步构建成一个复杂的对象。将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。 使用场景:一些基本部件不会变,而其组合经常变化的时候,需要生成的对象具有复杂的内部结构。
- 肯德基的汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。
- 用PC的主板、CPU、内存、显卡等零件组装电脑
ruby中建造者模式的实现
# 例子:画一个小人
# builder:为创建一个产品对象的各个部件指定抽象接口
module PersonBuilder
def head; end
def body; end
def arm; end
def leg; end
end
# 瘦子 ConcreteBuilder:实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示
class PersonThinBuilder
include PersonBuilder
def head
puts '头'
end
def body
puts '瘦身体'
end
def arm
puts '双手'
end
def leg
puts '双脚'
end
end
# 胖子
class PersonFatBuilder
def head
puts '头'
end
def body
puts '胖身体'
end
def arm
puts '胖手'
end
def leg
puts '胖脚'
end
end
# 指挥者类 构造一个使用Builder接口的对象
class PersonDirect
def initialize(person)
@person = person
end
# 指挥具体类的创建过程
def create_person
@person.head
@person.body
@person.arm
@person.leg
end
end
puts '这是瘦子:'
p1 = PersonThinBuilder.new
pd1 = PersonDirect.new(p1)
pd1.create_person
puts '这是胖子:'
p2 = PersonThinBuilder.new
pd2 = PersonDirect.new(p2)
pd2.create_person
原型模式
特点:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 使用场景:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
- 资源优化:类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
ruby中原型模式的实现
# ruby中的对象拷贝就是对原型模式的实现
# 浅拷贝: 只会拷贝第一层, 第二层的内容不会拷贝
3.1.0 :011 > arr1 = ['a', 'b', [1,2,3]]
=> ["a", "b", [1, 2, 3]]
3.1.0 :012 > arr2 = arr1.clone
=> ["a", "b", [1, 2, 3]]
3.1.0 :013 > arr1[2] << 4
=> [1, 2, 3, 4]
3.1.0 :014 > arr2
=> ["a", "b", [1, 2, 3, 4]]
# 深拷贝:拷贝所有层次的值,拷贝后的对象操作和原对象没有任何羁绊(关联)
3.1.0 :015 > arr3 = ['a', 'b', [1,2,3]]
3.1.0 :016 > arr4 = Marshal.load(Marshal.dump(arr3))
=> ["a", "b", [1, 2, 3]]
3.1.0 :017 > arr3[2] << 666
=> [1, 2, 3, 666]
3.1.0 :018 > arr4
=> ["a", "b", [1, 2, 3]]
适配器模式
特点:作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。 使用场景:将一个类的接口转换成客户希望的另外一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- Rails的ActiveRecord通过适配器屏蔽了MySQL、Sqlite、PgSQL的不同。
- 日常生活中的电压是220V,手机是一般是5V,手机的充电器就是电源适配器。
- 在 LINUX 上运行 WINDOWS 程序。
- JAVA 中的 jdbc链接不同的数据库。
ruby中适配器模式的实现 copyfuture.com/blogs-detai…
# 例子:将220v电压转为5v
# 受改造者
class Adaptee
def output_220v
return 220
end
end
# 目标接口
module Target
def output_5v; end
end
# 适配器
class Adaptor
include Target
attr_accessor :adaptee
def initialize(adaptee)
@adaptee = adaptee
end
def output_5v
prev = adaptee.output_220v
p '原始电压:220v -> 输出电压:5v'
return 5
end
end
adapter = Adaptor.new(Adaptee.new)
adapter.output_5v
# 原本不能使用的Adaptee类通过Adaptor适配器转换后就可以使用了
桥接模式
特点:用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。 使用场景:实现系统可能有多角度分类,每一种分类都有可能变化,可以把这种多角度分离出来,让它们独立变化,减少它们之间的耦合。
- 智能机的手机应用商店
- PC的硬件和软件
- 跨平台的视频播放器
ruby中桥接的实现 www.bbsmax.com/A/Vx5MDjqGJ…
# 例子:手机的分类和品牌问题,屏幕分类:直板、曲面、折叠等,品牌:华米OV等
# 品牌:抽象层
class Brand
def call; end
end
# 手机分类:抽象层
class Phone
attr_accessor :brand
def initialize(brand)
# 关联到具体的品牌
@brand = brand
end
def call; end
end
class Xiaomi < Brand
def call
p 'xiaomi'
end
end
class Huawei < Brand
def call
p 'huawei'
end
end
# 折叠形态的手机
class FoldPhone < Phone
def initialize(brand)
super(brand)
end
def call
@brand.call
p '折叠屏手机'
end
end
phone = FoldPhone.new(Xiaomi.new)
phone.call
# "xiaomi"
# "折叠屏手机"
# 实现了屏幕分类和品牌的解耦,使其可以独立变化
过滤器模式
特点:属于结构型模式,使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来,通过过滤得到自己需要的对象。 使用场景:实现系统可能有多角度分类,每一种分类都有可能变化,可以把这种多角度分离出来,让它们独立变化,减少它们之间的耦合。
- Logstash日志数据处理工具,可以定义过滤器对日志数据进行加工处理
- web应用中对请求数据或返回数据进行过滤
- Rails中有多种过滤器可以在action处理前、后对请求进行过滤处理
ruby中过滤器模式的实现
# 根据性别和姓名来过滤用户
class Person
attr_accessor :name, :sex
def initialize(name, sex)
@name = name
@sex = sex
end
end
# 接口:过滤器
module Filter
def filter; end
end
# 过滤性别为man的人
class Man
include Filter
def filter(persons)
tmp = []
persons.each { |p| tmp << p if p.sex == 'man'}
tmp
end
end
class NameLength
include Filter
def filter(persons)
tmp = []
persons.each { |p| tmp << p if p.name.length > 4}
tmp
end
end
person_list = [Person.new('zhangsan', 'man'), Person.new('xiaomei', 'women'), Person.new('lisi', 'man')]
p Man.new.filter(person_list)
p NameLength.new.filter(person_list)
# [#<Person:0x00007f4772d75030 @name="zhangsan", @sex="man">, #<Person:0x00007f4772d74ea0 @name="lisi", @sex="man">]
# [#<Person:0x00007f4772d75030 @name="zhangsan", @sex="man">, #<Person:0x00007f4772d74f40 @name="xiaomei", @sex="women">]
代理模式
特点:一个类代表另一个类的功能,属于结构型模式。 使用场景:为其他对象提供一种代理以控制对这个对象的访问。
- ruby的tempfile库的Tempfile类通过代理将文件处理方法委派到实际的IO对象上
- Vue的数据响应式实现中用到了代理模式
- Windows的快捷方式
ruby中代理模式的实现
# 小明让小李替他追小红(送礼物,花和巧克力)
# 送礼物公共接口
module SendGift
def send_flower; end
def send_chocolate; end
end
# 追求者
class Pursuit
include SendGift
attr_accessor :ns, :name
def initialize(ns, name='xiaoming')
# 要追求的女神
@ns = ns
@name = name
end
def send_flower
p "#{@ns.name}, 我替#{name}送你花"
end
def send_chocolate
p "#{@ns.name}, 我替#{name}送你巧克力"
end
end
# 代理类
class Proxy
include SendGift
attr_accessor :obj
def initialize(obj)
# 被代理的对象
@obj = obj
end
def send_flower
@obj.send_flower
end
def send_chocolate
@obj.send_chocolate
end
end
# 被追求者
class Girl
attr_accessor :name
def initialize(name)
@name = name
end
end
xiaohong = Girl.new('xiaohong')
xiaoli = Proxy.new(Pursuit.new(xiaohong))
xiaoli.send_flower
xiaoli.send_chocolate
# "xiaohong, 我替xiaoming送你花"
# "xiaohong, 我替xiaoming送你巧克力"
# ruby中通过标准库实现
require 'delegate'
class Origin
def demo
p '被代理对象的方法'
end
end
proxy = SimpleDelegator.new(Origin.new)
proxy.demo
组合模式
特点:又叫部分整体模式,将一组相似的对象组合成树形的层次结构, 用来表示部分以及整体层次关系。 使用场景:表示对象的部分-整体的层次结构,或者想忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象。
- HTML的DOM树结构是由标签组成的树形结构,父子、兄弟标签有一样的行为
- 管理系统中常见的公司组织架构,管理者与普通员工
- 计算机系统中的文件和文件夹的管理,父子文件夹的行为一致
ruby中组合模式的实现
# 例子:公司组织架构的角色树
class Employee
attr_accessor :name, :position, :staffs
def initialize(name, position)
@name = name
@position = position
@staffs = []
end
def add(emp)
@staffs << emp
end
def remove(emp)
@staffs.reject!{ |x| x == emp }
end
def work
p '认真工作'
end
end
ceo = Employee.new('John', 'CEO')
manager = Employee.new('weng', 'manager')
emp1 = Employee.new('tom', 'developer')
emp2 = Employee.new('mary', 'developer')
ceo.add(manager)
manager.add(emp1)
manager.add(emp2)
# 在这棵角色树上任意一个对象的属性和行为都是一致的
外观模式
特点:隐藏系统的复杂性,并向客户端提供了一个简单的访问系统的接口,属于结构型模式。 使用场景:降低访问复杂系统的内部子系统时的复杂度,简化客户端之间的接口。
- 生活中的基金投资,基金中可能包含股票、债券等投资品类,投资基金不需要关注基金内的投资组合,买卖基金就可以了
- Java的日志工具 slf4j为使用其他的日志框架提供一个可配置的接口
ruby中外观模式的实现
# 例子:基金投资
# 股票1
class Stock1
def buy
puts '股票1买入'
end
def sell
puts '股票1卖出'
end
end
# 股票2
class Stock2
def buy
puts '股票2买入'
end
def sell
puts '股票2卖出'
end
end
# 债券
class Bond
def buy
puts '债券买入'
end
def sell
puts '债券卖出'
end
end
# 基金类
class Fund
attr_accessor :s1, :s2, :b
def initialize
s1 = Stock1.new
s2 = Stock2.new
b = Bond.new
end
def buy
s1.buy
s2.buy
b.buy
end
def sell
s1.sell
s2.sell
b.sell
end
end
fund = Fund.new
fund.buy
fund.sell
享元模式
特点:用于减少创建对象的数量,以减少内存占用和提高性能,属于结构型模式。 使用场景:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。是一种缓存的机制。
- python中的字符串缓存机制,对于常用的字符串会创建一个字符串对象的缓冲池,创建字符串时缓冲池中有就直接取,缓冲池中没有时才创建
- 数据库连接工具中的数据库连接池,使用数据库连接时先从连接池中获取,没有时才创建新的连接放进连接池
ruby中享元模式的实现
# 例子:画5种颜色的圆形来填充20个位置
# 形状
module Shape
def draw; end
end
# 圆形
class Circle
include Shape
attr_accessor :color
def initialize(color)
@color = color
end
def draw
p "画了#{@color}的圆形"
end
end
# 管理各种颜色圆形的工厂
class CircleFactory
CIRCLES = {}
# 获取的颜色存在时直接返回,不存在时创建并缓存下来
def self.get_circle(color)
return CIRCLES[color] if CIRCLES.has_key?(color)
CIRCLES[color] = Circle.new(color)
end
end
colors = ['red', 'blue', 'yellow', 'white', 'black']
20.times.each do
CircleFactory.get_circle(colors[rand(5)]).draw
end
装饰器模式
特点:向一个现有的对象添加新的功能,同时又不改变其结构,属于结构型模式。 使用场景:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
- Python、JS中可以通过装饰器给类或实例方法添加额外功能
- Flask框架通过装饰器实现路由映射、钩子函数等功能
ruby中装饰器模式的实现 www.uoften.com/article/129…
# 例子:统计一个方法的执行时间
# 方法1:传入被装饰对象
class Demo
def test
sleep(3)
end
end
class Decorater
attr_accessor :demo
def initialize(demo)
@demo = demo
end
def test
t1 = Time.now
demo.test
p "执行时间:#{Time.now - t1}"
end
end
Decorater.new(Demo.new).test
# 方法2:使用extend方法动态的混入模块,来进行装饰
module Decorater
def test
t1 = Time.now
super
p "执行时间:#{Time.now - t1}"
end
end
class Demo
def test
sleep(3)
end
end
demo = Demo.new
demo.extend(Decorater)
demo.test
# 将Decorater混入到Demo类中,调用demo.test时,实际上会去调Decorater的test方法
责任链模式
特点:为请求创建了一个接收者对象的链,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者。 使用场景:将请求的发送者和请求的处理者解耦了,客户端只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递。
- 业务系统中的审批流程、任务流程
- JS 中的事件冒泡
ruby中责任链模式的实现
# 例子:请假审批处理,1天以内主管审,3天以内cto审
# 请求类
class VacateRequest
attr_accessor :name, :days
def initialize(name, days)
@name = name
@days = days
end
end
# 请求处理者
class Approver
attr_accessor :name, :next_approver
def initialize(name)
@name = name
end
# 处理请求的方法
def process_request; end
# 设置下一个处理人
def set_next(nexter)
@next_approver = nexter
end
end
# 主管
class Director < Approver
def initialize(name)
super(name)
end
# 重写方法
def process_request(vac_req)
if vac_req.days <= 1
p "请假#{vac_req.days}天,提交给#{@name}审批"
return
end
@next_approver.process_request(vac_req)
end
end
# CTO
class Cto < Approver
def initialize(name)
super(name)
end
def process_request(vac_req)
if vac_req.days <= 3
p "请假#{vac_req.days}天,提交给#{@name}审批"
return
end
@next_approver.process_request(vac_req)
end
end
# 构造请求处理的链条
dire = Director.new('bruce.wu')
cto = Cto.new('w.deng')
dire.set_next(cto)
# 请假请求
vr_one = VacateRequest.new('xiaozhang', 1)
vr_three = VacateRequest.new('xiaohong', 3)
# 请求审批都会提交到bruce.wu,满足条件他会处理,不满足条件自动转到下一个审批人
dire.process_request(vr_one)
dire.process_request(vr_three)
# "请假1天,提交给bruce.wu审批"
# "请假3天,提交给w.deng审批"
命令模式
特点:请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令,属于行为模式。 使用场景:将调用操作的对象和实现该操作的对象解耦,二者通过命令交互,命令可以组合。常用于需要对行为进行记录、撤销或重做、事务等处理。
- 桌面应用的一些交互控件,会把操作封装成命令来处理。
- 多级undo操作,将最近执行的命令存在栈中,撤销时从栈中pop出命令执行
- 可以实现原子事务行为,使用命令对象保存前状态,命令执行失败可以恢复到原来的状态
ruby中命令模式的实现 www.qb5200.com/article/303…
# 例子:按钮绑定不同的命令
class Button
attr_accessor :name, :command
def initialize name, command
@name = name
# 命令对象
@command = command
end
def do_something
@command.execute
end
end
# 命令的基类
class Command
def execute; end
end
# 绘画命令
class PaintCommand < Command
def execute
"draw something"
end
end
# 语音命令
class VocalCommand < Command
def execute
"talk something"
end
end
paintCommand = PaintCommand.new
vocalCommand = VocalCommand.new
button = Button.new("button", paintCommand)
p button.do_something
button.command = vocalCommand
p button.do_something
# button可以方便的绑定不同的命令,将按钮和按钮实现的功能进行了解耦,二者通过命令交互
迭代器模式
特点:用于顺序访问集合对象的元素,不需要知道集合对象的底层表示,属于行为模式。 使用场景:用不同的方式遍历对象的集合,不需要知道对象内部的表示。
- 太常用了,以至于编程语言中基本都实现了,常用的字符串、数组、集合、哈希等数据结构
- 常用的数据库连接工具中的游标
ruby中迭代器模式的实现 www.codenong.com/16973624/
# ruby中的集合类:数组、哈希字符串等通过Enumerable模块中的Enumerator来实现的
# Enumerator中的each方法会返回一个迭代器,通过next可以获取下一个元素
3.1.0 :013 > arr = [1,2,3]
=> [1, 2, 3]
3.1.0 :014 > enum = arr.each
=> #<Enumerator: ...>
3.1.0 :015 > enum.next
=> 1
3.1.0 :016 > enum.next
=> 2
3.1.0 :017 > enum.next
=> 3
# 自己实现一个迭代器的类
class Fruit < Enumerator
attr_accessor :kinds
def initialize
@kinds = %w(apple orange pear banana)
end
# 重写each方法
def each
@kinds.each { |kind| yield kind }
end
end
Fruit.new.each do |f|
p f
end
中介者模式
特点:用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护,属于行为型模式。 使用场景:当对象与对象之间存在大量引用关系时会形成复杂的网状结构,可以使用中介者模式来解耦,将结构变成简单的星状结构。
- 聊天室应用,用户之间的通信通过聊天室来转发
- ORM框架可以实现不同数据库和不同应用之间的对象关系的映射
- Vue中的事件总线的通信方式,不同的组件通过事件总线来通信
ruby中介者模式的实现
# 例子:聊天室,所有用户都可以往聊天室发消息,所有用户都可以看到聊天室的消息
class ChatRom
def self.showMsg(user, msg)
p "#{user.name}: #{msg} #{Time.now}"
end
end
class User
attr_accessor :name
def initialize(name)
@name = name
end
def send_msg(message)
ChatRom.showMsg(self, message)
end
end
User.new('xiaoming').send_msg('hello, 我是小明')
User.new('xiaohong').send_msg('hello, nice to meet you')
解释器模式
特点:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 使用场景:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
- 动态语言Python、Ruby等的解释器
- 正则表达式
- 前端框架中的插值表达式
解释器模式很抽象且复杂,在实际的项目开发中并不常用。
观察者模式
特点:定义了 对象之间一对多的依赖 , 令多个观察者对象同时监听某一个主题对象 , 当主题对象发生改变时 , 所有的观察者都会收到通知并更新 。 使用场景:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
- Vue中数据响应式的实现,观察者对象监听数据的变化后会触发引用数据的地方重新渲染DOM
- 公众号、博客网站的专栏订阅与通知机制
- 网站的站内消息
ruby中观察者模式的实现
# 例子:博客网站专栏
# 专栏
class Subject
attr_accessor :observers, :name
def initialize(name)
@name = name
@observers = []
end
# 添加观察者(订阅者)
def subscribe(user)
@observers << user
end
def change(new_name)
@name = new_name
self.notify
end
# 通知订阅者
def notify
@observers.each do |obs|
obs.update
end
end
end
class Observer
def initialize(name, subject)
@name = name
@subject = subject
end
def update; end
end
class User < Observer
def initialize(name, subject)
super(name, subject)
self.observe
end
def update
p "#{@subject.name} 更新了"
end
# 订阅主题
def observe
@subject.subscribe(self)
end
end
ruby = Subject.new('ruby教程')
xiaoming = User.new('xiaoming', ruby)
xiaohong = User.new('xiaohong', ruby)
ruby.change('设计模式大法')
# "设计模式大法 更新了"
# "设计模式大法 更新了"
状态模式
特点:允许对象在内部状态改变时改变对象的行为。 使用场景:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
- 视频播放器有暂停 、播放 、快进 、停止 等状态,每个状态对应不同的行为
- 电商购物系统中的订单有等待支付、付款完成、等待出库、退款中、订单完成等状态
ruby中状态模式的实现 blog.csdn.net/shulianghan…
# 例子:播放器的几种状态
# 状态父类
class State
attr_accessor :context
def set_context(context)
# 状态控制类的对象,控制状态转换
@context = context
end
# 播放
def play; end
# 暂停
def pause; end
# 倍速
def speed; end
end
# 播放状态
class PlayState < State
def to_s
p '当前状态:播放'
end
def play
p '正常播放'
end
def pause
self.context.set_state(PauseState.new)
end
def speed
self.context.set_state(SpeedState.new)
end
end
# 暂停状态
class PauseState < State
def to_s
p '当前状态:暂停'
end
def play
self.context.set_state(PlayState.new)
end
def pause
p '暂停播放'
end
def speed
p '停止状态不能倍速播放'
end
end
# 倍速状态
class SpeedState < State
def to_s
p '当前状态:倍速'
end
def play
self.context.set_state(PlayState.new)
end
def pause
self.context.set_state(PauseState.new)
end
def speed
p '倍速播放'
end
end
# 状态控制类
class Context
attr_accessor :current_state
def set_state(state)
# 状态对象
@current_state = state
@current_state.context = self
end
def play
@current_state.play
end
def pause
@current_state.pause
end
def speed
@current_state.speed
end
end
contex = Context.new
contex.set_state(PlayState.new)
contex.current_state.to_s
contex.play
contex.pause
contex.current_state.to_s
contex.speed
contex.current_state.to_s
contex.play
contex.current_state.to_s
contex.speed
contex.current_state.to_s
# "当前状态:播放"
# "正常播放"
# "当前状态:暂停"
# "停止状态不能倍速播放"
# "当前状态:暂停"
# "当前状态:播放"
# "当前状态:倍速"
策略模式
特点:一个类的行为或其算法可以在运行时更改,这种类型的设计模式属于行为型模式。 使用场景:在有多种算法相似的情况下,优化使用 if...else 所带来的复杂和难以维护的问题。
- 外卖、购物商城等优惠方式的判断:满减、折扣、红包、积分等
- 优化判断分支过多难以维护的代码
- 用地图查看去目的地的路线:公交、地铁、打车、骑行、步行等都是不同的算法
ruby中策略模式的实现
# 例子:计算不同优惠方式优惠后的金额
# 优惠策略类
class Strategy
attr_accessor :money
def initialize(money)
@money = money
end
def get_result; end
end
# 折扣
class DiscountStrategy < Strategy
def initialize(money)
super(money)
end
def get_result(discount)
p "折扣优惠后:#{money * discount}"
end
end
# 优惠券
class CouponsStrategy < Strategy
def initialize(money)
super(money)
end
def get_result(amount)
p "用优惠券后:#{money - amount}"
end
end
# 使用策略类的类
class Context
attr_accessor :strategy
def initialize(strategy)
@strategy = strategy
end
def excute(arg)
strategy.get_result(arg)
end
end
money = 300
context = Context.new(DiscountStrategy.new(money))
context.excute(0.8)
context.strategy = CouponsStrategy.new(money)
context.excute(0.8)
# "折扣优惠后:240.0"
# "用优惠券后:299.2"
模板模式
特点:一个抽象类公开定义了执行它的方法的模板,并允许子类为一个或者多个步骤提供实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤,属于行为性设计模式。 1、一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。 2、各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。 使用场景:将不变的公共代码抽象到父类中,避免重复,并将可变的行为留给子类来实现。
- Java中的InputStream、AbstractList等工具类的实现
- Java中的HttpServlet、JUnit TestCase等
ruby中模板模式的实现
# 父类:游戏
class Game
def init_game; end
def start_game; end
def end_game; end
# 模板方法,所有子类都调这个方法来构建子对象
def play
self.init_game
self.start_game
self.end_game
end
end
# 子类:棋牌游戏
class ChessGame < Game
attr_accessor :name
def initialize(name)
@name = name
end
def init_game
p "#{@name}游戏初始化"
end
def start_game
p "游戏开始"
end
def end_game
p "游戏结束"
end
end
# 子类:射击游戏
class ShootingGame < Game
attr_accessor :name
def initialize(name)
@name = name
end
def init_game
p "#{@name}游戏初始化"
end
def start_game
p "游戏开始"
end
def end_game
p "游戏结束"
end
end
ChessGame.new('象棋大师').play
ChessGame.new('奇幻射击').play
# "象棋大师游戏初始化"
# "游戏开始"
# "游戏结束"
# "奇幻射击游戏初始化"
# "游戏开始"
# "游戏结束"
与建造者模式的区别:
- 建造者是使用组合方式实现不同的表示,而模板方法使用的是继承的方式,组合优于继承。
- 建造者抽象的是构建过程,而模板方法提取的是实现公共的方法。
访问者模式
特点:用于封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素新的操作,这种类型的设计模式属于行为型模式。 使用场景:稳定的数据结构和易变的操作耦合问题,对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
- 数码产品系统更新升级,不改变硬件,给硬件修改或添加新的功能
- Java中List , Set , Map 等数据结构中 , 数据结构与数据操作分离
ruby中访问者模式的实现
# 例子:智能机器人的软件升级
# 硬件接口
class Hardware
attr_accessor :command
def initialize(command)
@command = command
end
# 执行命令
def run
p @command
end
# 将硬件交给访问者来管理
def accept(visitor); end
end
class Cpu < Hardware
def initialize(command)
super(command)
end
def accept(visitor)
visitor.visit_cpu(self)
end
end
class Disk < Hardware
def initialize(command)
super(command)
end
def accept(visitor)
visitor.visit_disk(self)
end
end
# 机器人
class Robot
attr_accessor :cpu, :disk
def initialize
@cpu = Cpu.new('1+1=1')
@disk = Disk.new('写入:1+1=1')
end
def calculate
@disk.run
@cpu.run
end
# 升级软件的方法,传入更新包对各个硬件进行升级
def update(visitor)
@disk.accept(visitor)
@cpu.accept(visitor)
end
end
# 定义访问硬件的接口
module Visitor
def visit_cpu(cpu); end
def visit_disk(disk); end
end
# 更新的软件包
class UpdateVisitor
include Visitor
# 将cup和磁盘对象传入访问者中进行升级
def visit_cpu(cpu)
cpu.command = '1+1=2'
end
def visit_disk(disk)
disk.command = '写入:1+1=2'
end
end
robot = Robot.new
robot.calculate
visitor = UpdateVisitor.new
robot.update(visitor)
robot.calculate
# "写入:1+1=1"
# "1+1=1"
# "写入:1+1=2"
# "1+1=2"
总结
- 设计模式是一种指导思想,合理的使用可以设计出结构良好,易于理解,易于维护和扩展的软件。
- 设计模式是个好东西,多用于代码重构中,需要有一定的软件开发经验的积累才能驾驭。对于普通的开发人员建议先了解使用场景,从简单的设计模式入手,在日常工作中的寻找合适的场景去实践,然后逐步深入理解。
- 滥用设计模式也会导致软件的复杂性上升,我们在使用时应该在扩展性和可用性上进行权衡。
参考资源: CSDN专栏:blog.csdn.net/shulianghan… 菜鸟教程:www.runoob.com/design-patt… B站视频:space.bilibili.com/59546029/ch… 书籍:《大话设计模式》