设计模式及设计规则详解(1)
设计模式
什么是设计模式?
是在系统设计中针对场景的一种方案,是解决功能逻辑开发的一种共性问题。
设计模式学习的是什么?
学习的是设计模式的思想,不能直接进行硬套
设计原则
- 单一职责原则(单一功能原则):每一个类中应该只有一个发生变化的原因,每个类中只实现一个功能。
-
模拟场景
视频网站不同的用户看视频的效果是不一样的,用户可分为访客用户、普通用户、vip客户等,例如访客用户只能看到480p的视频,vip用户可以看到2k视频等,下面进行最普通的实现。
class VideoService(object): def __init__(self) -> None: pass def WatchVideo(self, user_type): if user_type == "访客用户": print("480p") elif user_type == "普通用户": print("720p") elif user_type == "VIP用户": print("1080p")
测试代码如下
from base import VideoService if __name__ == "__main__": video = VideoService() video.WatchVideo("访客用户") video.WatchVideo("普通用户") video.WatchVideo("VIP用户") 480p 720p 1080p
上述使用了最基础的if else方法,通过用户类型进行逻辑调用,变化的点目前只有传入的用户类型,实现的功能包含用户的类型区分以及不同用户观看视频的处理逻辑两个功能。
突然有一天,需要对不同的用户做观看广告的区分,使用if else方法时,则需要对每一层if else中做处理,此时,变化的点为传入的用户类型以及不同用户类型的处理逻辑,不符合单一职责原则,并且如果if else比较多的话,代码的可维护性就会降低很多。
因此可以遵循单一职责原则,将不同用户的视频处理逻辑分别基于抽象类或者基类生成不同的用户视频处理类,当有地方需要使用时,只需要调用对应的用户类即可,并且当某个用户的行为发生变化时,只需要修改对应的类即可。修改如下:
基类: 用于提供实现不同用户类方法
class VideoService(object): def definition(self): """清晰度""" pass def advertisement(self): """广告""" pass
不同用户的实现类
from video_service import VideoService class VistorVideoService(VideoService): def definition(self): print("48p") def advertisement(self): print("30s广告")
from video_service import VideoService class NormalVideoService(VideoService): def definition(self): print("720p") def advertisement(self): print("10s广告")
from video_service import VideoService class VipVideoService(VideoService): def definition(self): print("1080p") def advertisement(self): print("无广告")
测试类
from vistor_video_service import VistorVideoService from normal_video_service import NormalVideoService from vip_video_service import VipVideoService if __name__ == "__main__": vistor = VistorVideoService() vistor.definition() vistor.advertisement() normal = NormalVideoService() normal.definition() normal.advertisement() vip = VipVideoService() vip.definition() vip.advertisement() 48p 30s广告 720p 10s广告 1080p 无广告
上述的用户类分别只复杂一个用户类型的功能,符合单一职责原则,当需要进行部分用户规则修改的时候,只需修改对应的用户类中的逻辑即可。
-
总结
单一职责原则适用于同一种事物的不同类型会导致的不同的结果,例如用户登录时不同用户的权限控制。
- 开闭原则: 对扩展开放,对修改关闭。
-
模拟场景
假设我们需要计算正方形、圆形等面积,目前我们需要提供一个sdk或者说是模块提供圆形的面积计算。基类如下:
class CircularCompute(object): def __init__(self) -> None: self.pai = 3.14 def compute_circular(self, r): return self.pai * r * r
当不同用户的使用时,圆形的面积默认情况下是派r平方,但是派又有不同的精度,可能不同人使用需要的精度不一样,可以继承此基类修改pai的精度重新进行计算,如下:
from circular_compute import CircularCompute class Extend_Compute(CircularCompute): def __init__(self) -> None: super().__init__() self.pai = 3.1415926 def compute_circular(self, r): return super().compute_circular(r) # 测试 if __name__ == "__main__": compute = Extend_Compute() ret = compute.compute_circular(2) print(ret)
上述继承自计算面积类,并重新设置了派的值,这样不同用户之间的调用就不会相互影响了。
-
总结
开闭原则规定了提供的sdk或者说是模块,当用户使用时,只能基于已有sdk做扩展,而无法直接修改其中的源码,保证了sdk或模块的独立性,从而尽可能地保证sdk或者模块地通用性,此原则个人理解是想尽可能地提高代码的可复用性、通用性,因此适用于代码需要复用或者说是通用的情况,对于一些内部逻辑或者较少使用的情况,不建议再基于基类进行继承扩展。
工厂模式
是创建型模式,在父类中提供创建对象的方法,允许子类决定实例化对象。简单点来说就是不暴露创建对象的具体逻辑,而是将将逻辑封装在一个函数中,在Java中相当于对new进行了封装,可用于不同环境下返回不同的实例对象,从而提供不同的处理方式。工厂返回的是实例对象,只是实例对象可能是由不同的子类去实例化的。工厂模式,又分为简单工厂模式、工厂方法模式、抽象工厂模式。
-
模拟场景
此处使用上述单一职责原则示例,用户最终的动作都是查看视频,不同用户的动作均是基于基类实现,此时,当我们需要进行不同用户权限的逻辑时,可以使用如下代码:
- 简单工厂模式
from normal_video_service import NormalVideoService from vip_video_service import VipVideoService from vistor_video_service import VistorVideoService class CreateFactory(object): def createVideoService(self, user_type): if user_type == "访客用户": return VistorVideoService() elif user_type == "普通用户": return NormalVideoService() elif user_type == "VIP用户": return VipVideoService() else: return None
创建了一个工厂类,返回不同用户的实例化对象,上述使用的是最基础的if else方法依据不同的用户类型实例化不同的对象,当然我们也可以将不同的实例对象与用户类型定义为一个字典,通过字典的获取方式去获取。
UML图如下:
简单工厂模式提供了工厂的方法,调用时只需传入需要的类型,即可实现对象的实例化优点:将对象的创建交由工厂去创建,实现了对象的创建与对象的实现的分离
缺点:不够灵活,当我们需要去新增具体的类型时,需要修改原有的工厂类,添加一个if else判断,并且当产品的类型比较多时,如果使用if else,维护起来会比较麻烦,当然也可以通过一个公共的字典去维护这些。
下面将简单使用字典来替换if else,代码如下:
user_video_dict = { "访问用户": VistorVideoService(), "普通用户": NormalVideoService(), "VIP用户": VipVideoService(), } class CreateFactoryExtend(object): def createVideoService(self, user_type): return user_video_dict.get(user_type, None)
当有新的产品时,可以添加到字典中,无需再手动修改工厂类。
- 工厂方法模式
需要创建一个抽象工厂类或者说是基类,相比较简单工厂模式,需要为不同的产品分别创建工厂(基于抽象工厂类),一个工厂只会产生一种产品。
还是基于上述的单个职责原则的示例,需要创建一个基类工厂,之后为不同的用户视频操作类创建不同的工厂,代码如下:
# 基础工厂类 class BaseFactory(object): def createFactory(self): pass # 访客用户工厂类 from base_factory import BaseFactory from vistor_video_service import VistorVideoService class VistorFactory(BaseFactory): def createFactory(self): return VistorVideoService() # 普通用户工厂类 from base_factory import BaseFactory from normal_video_service import NormalVideoService class NormalFactory(BaseFactory): def createFactory(self): return NormalVideoService() # vip用户工厂类 from base_factory import BaseFactory from vip_video_service import VipVideoService class VipFactory(BaseFactory): def createFactory(self): return VipVideoService()
当使用时只需要调用对应的工厂类返回对应的实例对象即可,代码如下:
from normal_factory import NormalFactory if __name__ == "__main__": # 创建工厂 normal_factory = NormalFactory() # 创建实例化对象 obj = normal_factory.createFactory() obj.definition() obj.advertisement() 720p 10s广告
UML图如下:
工厂方法提供了方法的工厂,由程序主动去调用对应的工厂,从而获取对应的实例化对象优点: 符合开闭原则,当需要新增产品时,直接基于工厂基类创建新的工厂类即可,当然,工厂类中不同对象类也需要实现
缺点: 当需要新增产品的数量较多时,会出现较多的工厂类- 抽象工厂模式
前面说到,工厂方法模式是一个工厂生产一种产品,抽象工厂则是一个工厂可以生产两种及多种产品。此处暂时搁置
重点
工厂模式必须要有工厂对象、产品基类/抽象产品类、具体的产品类(基于产品基类)、工厂基类/抽象工厂类(只会出现在工厂方法模式和抽象工厂模式), 简单工厂模式UML示意图如下:
需要注意的是工厂模式最终返回的是实例对象,工厂模式类似这么一个场景:有一个盒子,其中存放了很多大小一致的球,用户根据自己的选择拿取其中的一个球,然后可以拿球去兑奖;盒子相当于是工厂对象,球相当于是具体的产品类,球对应的兑换的产品则相当于是具体产品类中的逻辑,大小一致的球表明是按照一个特定的球去设计的,那个特定的球相当于是产品基类。
适用场景
- 需依赖不同环境创建不同的实例,且实例具有相同的行为,例如上述描述的不同用户的权限访问、抽奖时的不同奖品(实物、虚拟卡券等)。
- new操作的简单封装,依据不同的环境获取不同的实例对象。
建造者模式
将一个复杂对象的构建与表示分离,从而使得不同的构建过程有不同的表示。它是将复杂对象分解为多个简单的对象,然后一步一步进行构建的,类似于肯德基快餐的不同套餐的搭配。
- 模拟场景
实现肯德基的套餐