python项目设计之 接口 抽象类抽象方法 组合 依赖注入 程序设计原则 企业应用设计
1 接口概念
说接口概念之前我们先说一下在其他语言中不叫继承对于基类和派生类,而叫实现。
接口是派生类实现接口类而进行约束派生类的。接口中的方法不能写功能,而派生类实现了接口就需要把接口的所有方法都实现一遍。
# 其他语言继承叫实现
接口的约束性
# 其他语言接口类型 interface 只要继承(实现)接口类,那么派生类(子类)中必须有这个基类(父类)的方法 所以接口是用来约束的。 # interface 接口: # # def f1(self): # pass # # class lei(接口): # # def f1(self): # print(123)
python中没有接口类型
python中没有接口类型,单是我们可以人为的创造 约束给类。 raise 抛出异常来强制实现基类方法来做接口 用来约束
# 2 python中没有接口类型,单是我们可以人为的创造 约束给类。 raise 抛出异常来强制实现基类方法来做接口 用来约束 # # """ # # # python 接口构造 # # class IOrderRepository: # # def fettch_one_by(self,nid): # """ # #获取单条数据 # :param nid: # :return: # """ # raise Exception("子类中必须实现该方法") # # class OrderRepository(IOrderRepository): # # def __init__(self): # pass # def fettch_one_by(self,nid): # pass # # obj = OrderRepository() # obj.fettch_one_by(1)
# # 接口类 必须实现 切不能写任何代码,其他语言写了就报错,python我们规定不写。但不会报错 # class IFoo: # def f1(self): # """ # # :return: # """ # raise Exception("子类必须实现该方法") # # class Bar(IFoo): # def __init__(self): # pass
2 抽象类 抽象方法
1 抽象类中有普通方法,抽象方法:普通方法用来继承(普通类的特性),抽象方法用来约束(接口功能) 2 因此抽象类是普通类和接口类的整合 3 单独写抽象类没有意义,只有写了抽象方法,有了约束,子类继承时候必须实现抽象方法。 4 abc模块实现抽象类 5 如果一个抽象类全是抽象方法,则就是一个接口类了 全是普通方法,则就是一个普通类了
abc模块实现抽象类
import abc class Foo(metaclass=abc.ABCMeta): # 普通方法 用来实现继承 def f1(self): print("抽象类的普通方法") #抽象方法 @abc.abstractmethod def f2(self): """ 抽象方法 子类必须实现该方法 否则报错 :return: """ class Bar(Foo): def f2(self): print("这是子类中的抽象方法实现,不实现否则报错") obj = Bar() obj.f2()
3 组合
最开始如下:
我们都在init写死了。这样后期扩展很麻烦
# class Foo_bar: # def f1(self): # print("f1") # # class SQLHelper: # def __init__(self): self.f = Foo_bar() # # def fetch_all(self): # self.f.f1() # # class Service: # def __init__(self,): self.s = SQLHelper() # # def do1(self): # self.s.fetch_all() # # sv = Service() # # sv.do1()
接下来我们怎么做呢?:
将类实例化为对象传入构造方法
# class Foo_bar: # def f1(self): # print("f1") # # class SQLHelper: # def __init__(self,foo_bar): # self.foo_bar = foo_bar # # def fetch_all(self): # self.foo_bar.f1() # # class Service: # def __init__(self,sqlhelper): # self.sqlhelper = sqlhelper # # def do1(self): # self.sqlhelper.fetch_all() # # fb = Foo_bar() # sqlh = SQLHelper(fb) # # sv = Service(sqlh) # # sv.do1()
上述代码弊端,我们如果要是多个依赖,就需要手动完成传参,这样岂不是很费劲。??怎么办呢??
接下来就要将 依赖注入 但是讲解依赖注入之前我们先了解一下类是怎么创建的。
# # 依赖注入: 类创建,过程,在执行init之前执行在指定type类中的call方法中指定 # # class MyType(type): # def __call__(cls, *args, **kwargs): # # obj = cls.__new__(cls,*args,**kwargs) # print(obj,123124214) # if type(obj) == Foo: # print(11111) # else: # print(222222) # print("========先======") # # 如下两种 可以在写这个之前 做一些操作,比如判断是什么类,传入什么参数等待等。 # #cls.__init__(obj,*args,**kwargs) # obj.__init__(*args,**kwargs) # return obj # # # # class Foo(metaclass=MyType): # # def __init__(self,name): # print("========后======") # self.name = name # # # def f1(self): # print(self.name) # # # obj = Foo(123) # obj.f1()
解释说明: 一切皆对象。 类也是type的对象。
# ''' # 这里需要注意的:类是有type实例化的,一次类也是一个type类的对象。 而对象加括号,调用类的call方法,因此,下面的Foo(123) ,由于Foo是 # type类的实例化对象,因此Foo(123)会调用type的 __call__方法。 因此 args内有我们的传参123 # 所以cls是一个类名, 而call方法会调用Foo方法中的__new__方法,返回object.__new__ 赋值给obj 就是下面的obj # 而接下来执行Foo.__init__ # '''
4 依赖注入 实现 利用类创建的
# # 实现 依赖注入 多层调用不用传参的方式 class Mapper: __mapper_dict = {} @staticmethod def register(cls,vlaues): Mapper.__mapper_dict[cls] = vlaues @staticmethod def exists(cls): if cls in Mapper.__mapper_dict: return True return False @staticmethod def get_value(cls): return Mapper.__mapper_dict[cls] class MyType(type): def __call__(cls, *args, **kwargs): obj = cls.__new__(cls,*args,**kwargs) arg_list = list(args) if Mapper.exists(cls): arg_list.append(Mapper.get_value(cls)) # 如下两种 可以在写这个之前 做一些操作,比如判断是什么类,传入什么参数等待等。 #cls.__init__(obj,*args,**kwargs) obj.__init__(*arg_list,**kwargs) return obj class Foo(metaclass=MyType): def __init__(self,name): self.name = name def f1(self): print(self.name) class Bar(metaclass=MyType): def __init__(self,name): self.name = name def f1(self): print(self.name) Mapper.register(Foo,"foo") Mapper.register(Bar,"bar") obj = Foo() obj.f1() obj = Bar() obj.f1()
那么问题来了,类默认是由 type 类实例化产生,type类中如何实现的创建类?类又是如何创建对象?
答:类中有一个属性 __metaclass__,其用来表示该类由 谁 来实例化创建,所以,我们可以为 __metaclass__ 设置一个type类的派生类,从而查看 类 创建的过程。
class MyType(type): def __init__(self, what, bases=None, dict=None): super(MyType, self).__init__(what, bases, dict) def __call__(self, *args, **kwargs): obj = self.__new__(self, *args, **kwargs) self.__init__(obj) class Foo(object): __metaclass__ = MyType def __init__(self, name): self.name = name def __new__(cls, *args, **kwargs): return object.__new__(cls, *args, **kwargs) # 第一阶段:解释器从上到下执行代码创建Foo类 # 第二阶段:通过Foo类创建obj对象 obj = Foo()
五 程序设计原则
# 单一责任原则SRP # 一个对象只应该为一个元素服务: 比如你的数据库增删改查难道要放到多个类? # 开放封闭原则:OCP # 对于 装饰器,对内是封闭的,对外可以扩展 # 对于一个源码方法比如str类,它的源码我们尽量不改,我们用setattr(str,"k1",函数名) 这就实现了对外扩展该类 #其他语言叫extent # 里氏替换原则:LSP # 可以用任何派生类替换基类 # def (int i): # 可以是任何int的派生类 #print(i) #其他语言必须制定类型 可以是这个类型的任何派生类 #接口分离原则ISP #对于接口进行分类,避免一个方法过多 # 比如对于两个接口类 : 猫类(方法:喵喵叫 爬树) 狗类(方法:汪汪叫 摇尾巴) 这样我们继承接口类,进行约束只需要实现对应的就行。 #而如果这几个方法都写在一个接口类,那我们不需要用的方法也需要在派生类中实现。 # 依赖倒置原则 DIP #http://blog.csdn.net/zhengzhb/article/details/7289269 """ 定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。 问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。 解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。 依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。 """ # 依赖注入和控制翻转 # 上面已经搞了
6 企业应用设计
# 企业设计程序 分四层 #1 数据访问层 # DAO 一个表一个类 # repository 仓库 # 工作单元 增删改 用来做事务的 # 三个列表: # 查询对象 格式化创建sql #2 业务处理层 #a 业务逻辑组织 # 事务脚本 一个功能一个函数 经常用的 # 活动记录: 请求来, 在业务逻辑时候创建类,让业务的类跟数据库的的表也一致,根据数据库表字段来设计业务类, 直接对象交给orm。 # 适用于 博客和新闻系统 # # 领域模型 # 建模: 类的属性和行为 解决某件事就是领域。加起来就是领域模型 # 反模式 #不能有行为 也即是 类方法 # DDD : #根据事情驱动着你解决 # b 设计模式 # 工厂 策略 单例 #3 服务层: # 定义请求和响应的模式:各有规则 #4 表示层 UI
4、企业应用设计: 数据访问层 业务处理层 表示层 1、数据访问层 a. 数据访问策略 - DAO,通常一个表一个Dao类 - Repository,更高抽象层次上处理业务实体聚合 b. 数据访问模式 - unin of work(工作单元) - Query Object(查询对象),格式化规则,自动创建SQL 2、业务处理层 a. 业务逻辑组织 - Transcation Script模式(事务脚本模式) - Active Record(活动记录) - Domain Model(领域模型) - Anemic Domain Model(反模式) 类似于领域模型,但是反模式的领域对象中不包含任何行为,行为位于模型之外,让领域作为简单的数据传输类,领域服务更加巨细 - 领域驱动设计(Domain Driven Design) 领域驱动设计,一种流行的利用Domain Model 模式的设计方法学,简而言之DDD就是一组帮助人们构建能够反应业务并满足业务需求的应用程序的模式和原则。 b. 设计模式 - 工厂模式 - 策略模式 ...