面向对象的设计模式原则
1. 单一职责原则(Single Responsibility Principle)
2. 里氏替换原则(Liskov Substitution Principle)
3. 依赖倒置原则(Dependence Inversion Principle)
4. 接口隔离原则(Interface Segregation Principle)
5. 迪米特法则(Law Of Demeter)
6. 开闭原则(Open Close Principle)
7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)
单一职责原则(Single Responsibility Principle)
每一个类应该专注于做一件事情,单一职责原则可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;提高类的可读性,提高系统的可维护性;变更引起的风险降低,变更是必然的,如果遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用此原则。
例如一个登录类,
1、里面复制登录页面初始化设计
2、有获取数据库链接对象
3、对页面修改
4、查找用户
一个类承载了太多的职责不符合单一原则,应该将上面四个分开成四个表
开闭原则(Open Close Principle)
开闭原则规定软件中的对象(类、模块、函数等)对于扩展是开放的,但对于修改是封闭的,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。
开放封闭原则主要体现在两个方面:1. 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。2. 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
实现开开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的
例如一个登录类一开始是CircleButton圆形按钮(带 + display():void)办法,但是现在想将CircleButton圆形按钮改成RectangleButton矩形按钮(带 + view() : void),如果按他登录类LoginForm(- button : CircleButton, + display() : void)来说
1、想改成矩形,我们不但修改LoginForm类 display()去满足和实线矩形的view办法
2、我们还要CircleButton类名改成RectangleButton类名 这样就不符合开闭原则
我们就需要第二图的扩展
类LoginForm只需要写((- button : AbstractButton, + display() : void))
写一个抽象按钮类AbstractButton带display() :void 办法 ,圆形和矩形共同继承它,然后实线display()办法 ,
那么矩形类实现这个抽象类,LoginForm在向上转型就行了,然后就可以取到矩形类 根本不需要动代码 例如了继承和多态的机制
里氏替换原则(Liskov Substitution Principle)
# 它是开闭原则的具体实现手段之一,原理也是利用抽象类和接口去实现业务
# 概念:A类是B类的父类,B类是子类 A类是父类 C类是逻辑业务类(method() : String)
# 这个办法里面能接受或使用父类A 也一定能使用子类B 反之不可以,这个设计的最初条件是子类B的所有实现办法必须是在父类
# A都有声明的,否则不符合这个规则,例如子类B有一个encrypt(String text) A类没有这个办法声明 ,那么父类的对象就无法调用子类B独有的办法 就不符合里氏替换原则。
# 代码实现父类A是抽象类,和接口而且声明了B类的所有实现办法,这样父类对象就可以通过子类对象向上专型,从而调用子类的实现办法,又因为子类实现的办法父类都声明了,因此符合里氏替换原则。
实现逻辑例如子类实现父类办法(抽象类或者接口,子类所有办法父类都声明了),
1、父类A 父类对象a=new 子类B(); 向上转型
2、父类对象a.encrypt(); 调用子类实现的办法
3、子类所有办法父类都声明了
4、开闭原则原理跟他差不多
5、所以人们常说里氏替换是实现开闭原则的手段之一
依赖倒置原则(Dependence Inversion Principle)
# 该原则规定:1. 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。2. 抽象接口不应该依赖于具体实现。而具# 体实现则应该依赖于抽象接口。该原则颠倒了一部分人对于面向对象设计的认识方式。如高层次和低层次对象都应该依赖于相同的抽# 象接口。
#上图中,高层对象A依赖于底层对象B的实现;图2中,把高层对象A对底层对象的需求抽象为一个接口A,底层对象B实现了接口A,这就是依赖反转。
#依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖反转原则的核心。
#依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一层不变的。依赖于抽象,就是对接口编程,不要对实现编程。
# 简单的说:A类是具体实现类(仅有一个办法encrypt(String text)) ,B类是一个逻辑业务类
# B类单向关联了A类,也就是B类中调用A类的encrypt(String text)办法,如果是使用依赖倒置设计原则,这样就不对,就需要扩展
# 将A类也就是具体实现类抽象化成抽象类或者接口C,然后在C类中声明A类的办法encrypt(String text),A类实现抽象类或接口类C
# 然后B类单向关联抽象类C,也就是在B类创建一个私有的抽象或者接口类C的对象 然后初始化或setXXX(C类的子类对象,也就是A类对象),然后向上转型,在利用父类的对象调用子类实现父类的办法。
# 这就是依赖倒置,这就是对接口编程,不要对实现编程,
# 这就是对抽象类编程,不要对具体实现类编程,
接口隔离原则(Interface Segregation Principle)
# 提供尽可能小的单独接口,而不要提供大的总接口。
# 也就是要为各个类建立专用的接口,而不要试图去建立一个
# 很庞大的接口供所有依赖它的类去调用。依赖几个专用的接口要比
# 依赖一个综合的接口更灵活
例如 一个接口类AbstractService类,里面三个办法,
OperatorA():void
OperatorB():void
OperatorC():void
有三个实现类
ClientA
ClientB
ClientC
但是ClientA只是对OperatorA()办法进行实现,但是现在是实现胖接口也就是实现总接口,那么除了能看到OperatorA()办法
还能看到OperatorB()办法 ,OperatorC()办法,首先在一定程度上影响系统的封装性
其次代码也增多一些无关紧要的代码。
因此我们就需要利用接口隔离原则进行重构
将接口类AbstractService类,里面三个办法,
OperatorA():void
OperatorB():void
OperatorC():void
重构成三个接口
AbstractServiceA类,里面1个办法,
OperatorA():void
AbstractServiceB类,里面1个办法,
OperatorB():void
AbstractServiceC类,里面1个办法,
OperatorC():void
那么ClientA类只需要实现AbstractServiceA类中的OperatorA():void
办法即可,这样既保证系统良好的封装性,也不需要关心修改一个业务会对其他不相干的业务造成的影响
组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)==合成复用原则Composite Reuse Principle CRP
# 要尽量使用类的关联关系(包括组合|聚合关联关系),少用继承。
# 通过继承来实现复用很简单,而且子类可以覆盖父类的方法,易于扩展。
# 但其主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实现细节暴露给子类,
# 由于基类的某些内部细节对子类来说是可见的,所以这种复用又称为“白箱”复用。
# 如果基类发生改变,那么子类的实现也不得不发生改变;
# 从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性
# 继承只能在有限的环境下使用(例如类不能被final修饰)
# 为何说继承会将基类的实现细节暴露给子类,
# 在这里可以详细解说 https://blog.csdn.net/weixin_30542079/article/details/98090404
# 简单的说ServiceA类父类 ClientA类子类
ServiceA类父类有两个办法
add(int number):int
addall(int a, int b) :int
如果ClientA类继承ServiceA类,那么ClientA类就可以看到这两个办法,以及参数,
在利用子类覆盖父类办法 在利用super调用父类被覆盖的办法,就可以看到里面是相加还是相减 这就是细节
再者还可以在子类的覆盖办法中修改逻辑代码,例如父类本来就是相减 我们覆盖后改成相乘
从而改变里面代码
因此对系统的封装性造成一定程度上的影响,因此才说继承会将基类的实现细节暴露给子类
# 为何说 如果基类发生改变,那么子类的实现也不得不发生改变
根据上面addall(int a, int b) :int 本来是两个参数,
现在把父类改成三个参数 那么继承的子类也必须要改成三个参数,否则出错
因此说我们多用关联关系 少用继承关系
关联关系直接创建ServiceA对象 然后设置ClientA类里面添加一个私有的ServiceA serviceA对象
在利用初始化或者setxxx(ServiceA serviceA) 对他赋值 从而就可以直接调用ServiceA类里面的办法
也不知道里面的具体实现逻辑,只管用,
使用者只需要关注怎么用,而不需要关注内部是怎么实现的。这也就是封装的意义
#什么是封装呢?封装就是隐藏实现细节。使用者只需要关注怎么用,而不需要关注内部是怎么实现的
DBUtil类用于连接数据库,它提供了一个getConnection()方法,用于返回一个Connection类型的数据库连接对象。
由于在StudentDAO、TeacherDAO等类中都需要连接数据库,因此需要复用getConnection()方法,
StudentDAO TeacherDAO等数据访问类直接继承DBUtil类,复用其中定义的方法。
如果需要更换数据库连接方式,如原来采用JDBC连接数据库,现在采用数据库连接池连接,
则需要修改DBUtil 类源代码。
如果StudentDAO采用JDBC连接,但是TeacherDAO采用连接池连接,
则需要增加一个新的DBUtil类,并修改StudentDAO或TeacherDAO的源代码来复用新的DBUtil类的getConnection()方法
使之继承新的数据库连接类,这将违背开闭原则,系统扩展性较差。
上图中StudentDAO和TeacherDAC)类与DBUtil类不再是继承关系,而改为聚合关联关系,
并增加一个setDBOperator()方法来给DBUtil类型的成员变量dBOperator赋值。
如果需要改为另一种数据库连接方式,只需要给DBUtil增加一个子类,如 NewDBUtil,
在该子类中覆盖父类的getConnection()方法,
# 子类对象向上转型成父类对象 又因为子类重写或者覆盖了父类getConnection()办法
# 直接子类向上转型得到父类的对象再调用getConnection()办法
# 实际也是调用子类覆盖父类的getConnection()办法的代码逻辑
# 跟接口一样
Person person = new Chinese();
再在客户类中调用setDBOperator()方法时注入子类对象person即可
这样就可以得到自己想要的链接方式了
迪米特法则(Law Of Demeter)
写一个中间类 From1想跟
Dao下面的任意有联系
直接在中间控制类实现
这样就减少
两个实体类的
相互作用,从而降低
耦合度
让类与类之间保持松散的
耦合度
可以适当的增加中间控制类
版权说明
文章采用: 《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权。版权声明:未标注转载均为本站原创,转载时请以链接形式注明文章出处。如有侵权、不妥之处,请联系站长删除。敬请谅解!
常见资源合集和破解 fmvvvteih...