设计模式之禅笔记一(单一职责,里氏替换,依赖倒置)

鲨鱼笔记

1.单一职责原则
Singer Responsibility Principle简称SRP:有且仅有一个原因引起类或接口的变更!即一个类或接口只有一个职责,只负责一件事情。每个接口职责分明,结构清晰!
RBAC模型Role-Based Access Control,基于角色的访问控制:通过分配和取消角色来完成用户权限的授予和取消,使动作主体(用户)与资源的行为(权限)分离。
*例子:*打电话过程:拨号,通话,回应,挂机。
public interfaceIPhone{ //拨通电话 public void dial(String phoneNumber); //通话 public void chat(Object o); //通话完毕,挂电话 public void hangup(); }
其中Iphone接口包含了两个职责:dial()和hangup()实现协议管理,chat()实现数据传送,把我们说的转换成模拟信号或者数字信号传递给对方。协议接通和数据传送的变化都会影响这个接口或实现类的变化。两个原因引起变化,职责互相影响,于是要拆分成两个接口!
*好处:*类复杂性降低,可读性提高,可维护性提高,变更引起的风险降低,一个接口修改只对相应的实现类有影响,对其他接口无影响!方法,接口设计一定要单一,实现类则需要多方面考虑,工期,成本,技术水平,硬件。
2. 里氏替换原则
Java采用单一继承原则,C++采用多重继承规则,一个子类可以继承多个父类。看起来“利大于弊”,为了使“利”发挥到最大,“弊”减到最小,引入里氏替换原则Liskov Substitution Principle,LSP。*第一种定义:*如果对于每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都替换成o2时,P的行为没有发生变化,那么类型S是类型T的子类型。*第二种定义:*所以引用基类的地方必须能透明地使用其子类的对象。*通俗:*只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,有子类出现的地方,父类未必能适应。
例子
模仿CS游戏。

public abstract class AbstractGun{
//枪支抽象类,shoot()用来杀敌
public abstract void shoot();}
public class Handgun extends AbstractGun{
//手枪
public void shoot(){
System.out.println("手枪正在射击~~~~~~~~~~");}}
public class Rifle extends AbstractGun{
//步枪
public void shoot(){
System.out.println("步枪正在射击~~~~~~~~~~");}}
public class MachineGun extends AbstractGun{
//机枪
public void shoot(){
System.out.println("机枪正在射击~~~~~~~~~~");}}
public class Soldier{
//士兵的实现类
//定义士兵的枪支
private AbstractGun gun;
//给士兵一支枪
public void setGun(AbstractGun —gun){
this.gun=gun;}
public void killEnemy(){
System.out.println(“士兵正在杀敌人~”);
gun.shoot();}}

定义士兵使用枪支杀敌,但枪仍是抽象的,具体是那种枪,要在上战场前(场景中),通过setGun方法确定。
public class Client{ //场景类 public static void main (String[] args){ //产生吴亦凡这个士兵 Soldier fanFan=new Soldier(); //给凡凡一支枪,随便哪种枪都行 fanFan.setGun(new Rifle()); fanFan.killEnemy();}}
在编写程序的时候Soldier士兵类根本就不需要知道是哪种型号的枪(子类)被传入。注意在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。如果此刻有一个玩具手枪,继承于AbstractGun类,射击杀不死敌人,这个不能卸载shoot方法中。
public class ToyGun extends AbstractGun{ //玩具枪不能实现射击,但是编译器又让实现这个方法,怎么办?虚构一个! @Override public void shoot(){ //不能射击,方法就不实现了}}
引入新类,场景中也使用了该类,所以Client稍作修改public class Client{ public static void main(String[] args){ //凡凡登场 Soldier fanFan=new soldier(); fanFan.setGun(new ToyGun()); fanFan.killEnemy();}}
凡凡用玩具枪射杀敌人???没有子弹呀!业务调用类出现问题!业务逻辑不能运行,怎么办?*解决办法第一种:*在Soldier类中增加instanceof的判断,如果是玩具枪,就不用来杀敌人。虽然可以解决问题,但在程序中,每增加一个类,所有与这个父类有关系的类都必须修改,有点麻烦幺!*解决办法第二种:*ToyGun脱离继承,建立一个独立的父类AbatractToy,为实现代码复用,可以与AbastractGun建立关联委托关系。在AbatractToy中生命将声音,形状都委托给AbatractGun处理,然后两个基类下的子类自由延展,互不影响!所以要考虑子类是否能够完整地实现父类的业务!*注意:*如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,这时候建议断开父子继承关系,采用依赖,聚集,组合等关系代替继承。
契约里氏替换原则要求制定一个契约,就是父类或接口,这种设计方法也叫做Design by Contract(契约设计),也同时制定了前置条件和后置条件。子类中方法的前置条件必须与超累中被覆写的方法的前置条件相同或者更宽松。覆写或实现父类的方法时输出结果可以被缩小。
3.依赖倒置原则
Dependence Inversion Principle,DIP.定义:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。*解释:*每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,原子逻辑再组装就是高层模块。在Java语言中,抽象就是指接口或抽象类,两者都是不直接被实例化的。细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化。
依赖倒置原则在Java中的表现:
模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;接口或抽象类不依赖于实现类;实现类依赖接口或抽象类。----“面向接口编程”----OOD(Object-Oriented Design,面向对象设计)的精髓之一。
*优点:*减少类间耦合性,提高系统稳定性,降低并行开发引起的威胁,提高可读性和可维护性。
例子模拟吴亦凡开车

public class Driver{
//吴亦凡司机的主要职责是驾驶汽车
public void driver(Benz benz){
benz.run();}}
//司机通过调用奔驰车的run方法开动奔驰车
public class Benz{
//汽车一定会跑
public void run(){
System.out.println("奔驰车开始运行");
}}
//Client场景类产生相应的对象
public class Client{
public static void main(String[] args){
Driver fanFan=new Driver();
Benz benz=new Benz();
//凡凡开奔驰车
fanFan.driver(benz);}}

完成了司机凡凡开动奔驰车的场景,虽然不知道吴亦凡愿不愿意开奔驰车!但总归到目前,项目没有任何问题。public class BWM{ //宝马车当然也可以开动了 public void run(){ System.out.println("宝马汽车开始运行·········");}}

宝马汽车虽然开动了,但是凡凡却没有办法开动,因为凡凡没有开动宝马车的方法!导致司机类和奔驰车类是紧耦合的关系,系统的可维护性降低。
解决办法建立两个接口:IDriver和ICar。public interface IDriver{ //司机接口 //是司机就应该会驾驶汽车 public void driver(ICar car);} //司机类的实现 public class Driver implements IDriver{ public void driver(ICar car); car.run();}
在IDriver中,通过传入ICar接口实现了抽象之间的依赖关系,Driver实现类也传入了ICar接口,至于到底是哪个型号的Car,需要在高层模块中声明。public interface ICar{ //汽车接口 //是汽车就应该能跑 public void run():} public class Benz implements ICar{ public void run(){ System.out.println("奔驰汽车开始行驶·······");}} public class BWM implements ICar{ public void run(){ System.out.println("宝马汽车开始行驶·······");}}
在业务场景中贯彻“抽象不应该依赖细节”,即抽象(ICar接口)不依赖BWM和Benz两个实现类(细节),因此在高层次的模块中应用都是抽象。public class Client{ public static void main(String[] args){ IDriver fanFan=new Driver(); ICar benz=new Benz(): //凡凡开奔驰 fanFan.driver(benz);}}
Client属于高层业务逻辑,它对低层模块的依赖都建立在抽象上,fanFan的表面类型是IDriver,是一个接口,是抽象的,非实体化的,在后面的所有操作中,fanFan都是以IDriver类型进行操作,屏蔽了细节对抽象的影响。Benz的表面类型是ICar。要实现凡凡开宝马车也很容易,只要修改业务场景就?了!public class Client{ public static void main(String[] args){ IDriver fanFan=new Driver(); ICar bwm=new BWM(): //凡凡开宝马 fanFan.driver(bwm);}}
在新增加低层模块时,只修改了业务场景类,也就是高层模块,对其他的低层模块如Driver类不需要做任何修改,业务就可以运行,把“变更”引起的风险扩散降低到最小。*注意:*在Java中,只要定义变量就必然有类型,一个变量可以有两种类型:表面类型是在定义的时候赋予的类型,实际类型是对象的类型,如fanFan的表面类型是IDriver,实际类型是Driver。在思考依赖倒置对并行开发的影响,两个类之间有依赖关系,只要制定出两者之间的接口(或抽象类)就可以独立开发了,而且项目之间的单元测试也可以独立地运行,而TDD(Test-Driver Development,测试驱动开发)开发模式就是依赖倒置原则的最高级应用。依赖的三种写法:

  1. 构造函数传递依赖对象public interface IDriver{ public void driver(); } public class Driver implements IDriver{private ICar car;//构造函数注入public Driver(ICar _car){this.car=_car;}//司机驾车public void driver(){this.car.run();}}}
    2.Setter方法传递依赖对象
public interface IDriver{
//车辆型号
public void setCar(ICar car);
//司机会驾驶
 public void driver(); }
 public class Driver implements IDriver{private ICar car;public void setCar(ICar car){this.car=_car;}//司机驾车public void driver(){this.car.run();}}}

3.接口声明依赖对象
在接口的方法中声明依赖对象,接口生命依赖的方式,也叫接口注入。
使用这个原则:

  1. 每个类尽量都有接口或抽象类,或两者具备。这个是基本要求,有了抽象才能依赖倒置。
  2. 变量的表面类型尽量是接口或是抽象类。
  3. 任何类都不应该从具体类派生。也不是绝对的,容许你犯小错,只要不超过两层的继承都可以忍受。
  4. 尽量不要覆写基类的方法。若基类是一个抽象类,而且这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会产生影响。
  5. 结合里氏替换原则使用。

未完待续~~~~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值