设计模式之禅笔记

1.六项基本原则

1.1 单一职能原则(SRP)

单一职责(Single Responsibility Principle )原则的定义是:一个接口或者类有且仅有一个原因引起类的变化。
每一个类实现的功能和作用要单一,比如实体类实现的是单纯的属性和get,set方法,是为了能生成一个纯净的类。实现逻辑操作的要重新生成一个类,不要在实体类中给出复杂业务逻辑的操作。调用到业务逻辑的服务操作也要重新生成一个类,边界尽量清晰。
单一职责设计的好处:
1.类的复杂性将i,实现职责明确定义。
2.可读性提高
3.可维护性提高
4.变更引起的风险降低,变更是必不可少的,但如果一个接口修改只对应的实现类是有影响的。

1.1.1用户类图设计

一般用户设计类图会这样设计如图:
设计模式之禅笔记_第1张图片
但是这样的设计是不对的不符合单一职责的原则,应该把用户属性和 用户的业务分离开来可以这样:
设计模式之禅笔记_第2张图片
抽取两个接口,一个是收集和反馈用户属性信息,一个是负责用户的行为完成用户的信息和变更。
如何代码使用如下:

IUserInfo userInfo = new UserInfo() ;
IUserBo userBo = userInfo;
userBo.setPassword("abc");
userBiz userBiz = userInfo ;
userBiz.deleteUser() ;

但实际使用中更倾向于这种:

设计模式之禅笔记_第3张图片
项目中经常用到的SRP类图
抽象出IUserBo 和 IUserBiz
IUserBiz 依赖 IUserBo
引申:虚线的箭头表示依赖关系:A依赖类B(局部变量、方法的参数或者对静态方法的调用) 这里的A是IUserBiz,B为IUserBO

可能会这样使用:

IUserBiz userBiz = new UserBiz() ;
IUserBo userBo = new UserBo();
userBo.setPassword("abc") ;
userBiz.updateUser(userBo) ;

1.1.2电话类图设计

设计电话通话的过程:拨号、通话、挂机。写一个接口表示类图:
其中这里不仅仅是一个职责,拨号和挂机属于协议管理,通话属于数据传送。分为协议管理和数据传送这两个职责。一般设计时会认为手机有链接和传输的功能,而并非设计成 手机时一个连接器和传输器,故,需要思维转化所以使用单一职责的原则,
设计模式之禅笔记_第4张图片
这样设计虽然结构清晰,但这样设计有一点不好,就是ConnectionManager类和DataTransfer 类 属于组合关系,需要组合在Phone类中使用,组合是一种强耦合关系,你和我都有共同的生命周期,这样的强耦合关系不如直接使用接口实现,如下图
设计模式之禅笔记_第5张图片

1.1.3单一职责适用于方法设计

单一职责也适用于方法,比如有的方法参数很多,甚至可以不定数的传参。这样的设计不符合单一职责。
设计模式之禅笔记_第6张图片

而 应该这样设计:
设计模式之禅笔记_第7张图片

1.2 里氏替换原则(LSP)

里氏替换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。

子类可以继承父类的私有方法以外的所有方法和非私有的属性,重写可以覆盖掉父类中同名同参数的方法。

1.子类必须完全实现父类的方法。

2.子类可以有自己独立的属性和方法。

覆盖或实现父类的方法时输入参数可能会被放大。(如果子类给的参数范围大于父类,不会被执行到,要求子类给参数类型必须等于父类,从而达到复写)。

覆盖或者实现父类的方法时输出可以被缩小范围。(父类的返回参数类型必须大于子类)

在项目中,采用里氏替换原则时,尽量避免子类的“个性”,把子类当作父类来使用,子类的个性被抹杀——委屈了一点;然而把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺乏类替换的标准。

1.3 依赖倒置原则

使用接口,就是面向接口编程。“面向接口编程”——OOD(Object-Oriented Design)
定义:
1. 高层模块不因该依赖地层模块,两者都应该依赖其抽象;
2. 抽象不应该依赖细节;
3. 细节应该依赖抽象;
底层模块和高层模块: 不可分割的原子逻辑为底层模块。由多个实现原子逻辑组成的逻辑属于高层模块。
什么是细节: 细节就是实现类,实现接口或者继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,就是加一个关键字new产生一个对象。

依赖倒置原则在java中的体现就是:
1. 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的;
2. 接口或抽象类不依赖于实现类;
3. 实现类依赖接口或抽象类;
使用依赖倒置原则的好处:可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。

1.3.1 汽车驾驶员案例

设计模式之禅笔记_第8张图片
司机接口:

pubic interface IDriver{
 //是司机就应该会驾驶汽车
 public void drive(ICar car) ;
}

司机类的实现:

public class Driver implements IDriver{
//司机的主要职责就是驾驶汽车
public void drive(ICar car) {
		car.run() ;
}
}

汽车接口以及实现类:

public  interface ICar {
		// 是汽车就应该能跑
		public void run() ; 
		
}

public class Benz implements ICar{
  //汽车肯定会跑
    public void run(){
		System.out.println("奔驰汽车开起来了") ; 
	}
}
public class BMWimplements ICar{
    //宝马汽车能开起来
    public void run(){
		System.out.println("宝马汽车开起来了") ; 
	}
}

业务场景

public class Main{
 public static void main(String Args[]) {
	IDriver zhangsan= new Driver() ;
	ICar car = new Benz() ;
	
	zhangsan.drive(car) ;
	//当然zhangsan也可以驾驶 BMW
	ICar car = new BMW() ;
	zhansan.drive(car) ;
}
}

main属于高层业务逻辑,它对底层模块的依赖建立于抽象上。 后期如果张三想开别的车只需要增加汽车的避免了复杂的修改。

Java在运行时会出现两种类型:编译时类型和运行时类型。
编译时类型由声明该变量时使用的类决定,运行时类型,由实际赋给该变量的对象决定。两者不一致时会出现所谓的多态。

代码测试类:
测试驱动开发,先写好单元测试类,然后再写实现类,A程序员负责IDriver的开发,B程序员负责ICar 的开发,两个开发人员按照制定好的接口独立开发,结果 A 程序开发的比较快,完成了IDriver 类的开发工作,B程序员还没有完成开发。此时,A程序员可以使用JMock工具对抽象虚拟对象进行测试。如下代码:

public class DriverTest extends TestCase{
 		Mockery context = new JUnit4Mockery() ;
@Test
public void testDriver() {
	 final ICar car = context.mock(ICar.class); 
	 IDriver driver = new Driver() ;
	 //内部类
	 context.checking(new Expectations(){}{
		oneOf(car).run() ;
}});

driver.driver(car) ;
}

1.3.2 依赖的3种写法

依赖是可以传递的 A可以依赖B ,B可以依赖C …
但要知道一点:只要做到抽象的依赖,即使多层依赖传递也无所畏惧。
1.依赖的第一种写法: 构造函数传递依赖

public interface IDriver {
  //是司机就应该会驾驶
  public void driver();
 
}

public class Driver implements IDriver {
	private ICar car ;
	//构造函数注入
	public Driver(ICar _car){
	 this.car = _car 
}

//司机的职责就是开车
public void drive() {
		this.car.run() ;
}
}

2. 使用Setter注入
顾名思义就是使用Setter方法声明依赖关系,注入。

public interface IDriver {
  public void setter(ICar car) ;
  //是司机就应该会驾驶
  public void driver();
}


public class Driver implements IDriver {
	private ICar car ;
	//构造函数注入
	public Setter(ICar _car){
	 this.car = _car 
}

//司机的职责就是开车
public void drive() {
		this.car.run() ;
}
}

3.使用声明依赖对象

如1.3.1所示即可

1.4 接口隔离原则

1.4.1定义

接口的定义:

  1. 实例接口(Object Interface),在Java中声明一个类然后new产生一个实例。 例如Person zhangsan = new Person()
  2. 类接口(Class Interface) Java中经常使用的interface关键字定义的接口。
    隔离的定义:
  3. 客户端不应该依赖它不需要的接口。客户端需要什么接口就提供什么接口,把不需要的接口剔除掉,那就需要对接口进行细化,保证其纯洁性。
  4. 类间的依赖关系应该建立在最小的接口上。
    一句话定义概括就是说:建立单一接口,不要建立庞大的接口。接口尽量细化,同时接口中的方法尽量少。与单一职责不同的是,单一职责要求的是类和接口职责单一,注重职责是业务逻辑的划分,而接口隔离原则要求接口的方法尽可能少。

1.4.2星探寻美女案例

设计模式之禅笔记_第9张图片
AbstractSearcher :


public abstract class AbstractSearcher {
    protected IGoodBodyGirl goodBodyGirl ;
    protected IGreatTemperamentGirl greatTemperamentGirl ;

    public AbstractSearcher(IGoodBodyGirl _goodBodyGirl) {
        this.goodBodyGirl = _goodBodyGirl ;

    }

    public AbstractSearcher(IGreatTemperamentGirl _greateTemperamentGirl) {
        this.greatTemperamentGirl = _greateTemperamentGirl ;

    }

    //展示美女
    public abstract  void show() ;

}

IGoodBodyGirl 与 IGreatTemperamentGirl :

//长的好看的女孩
public interface IGoodBodyGirl {

    //长的好看
    public void goodLooking() ;
    //好的身材
    public void niceFigure() ;

}

//气质好的女孩
public interface IGreatTemperamentGirl {

    //要有气质
    public void greatTemprament() ;

}



PrettyGril :

public class PrettyGril implements IGoodBodyGirl,IGreatTemperamentGirl {

    private  String name ;
    public PrettyGril(String _name){
        this.name = _name ;
    }

    @Override
    public void goodLooking() {
            System.out.println(this.name+"脸蛋漂亮");
    }

    @Override
    public void niceFigure() {
        System.out.println(this.name+"体型漂亮");
    }

    @Override
    public void greatTemprament() {
        System.out.println(this.name+"很有气质");
    }
}

Seacher


public class Seacher extends AbstractSearcher {

    public Seacher(IGoodBodyGirl _goodBodyGirl) {
        super(_goodBodyGirl);
    }

    public Seacher(IGreatTemperamentGirl _greateTemperamentGirl) {
        super(_greateTemperamentGirl);
    }

    @Override
    public void show() {
        if(super.goodBodyGirl!=null) {
            super.goodBodyGirl.goodLooking();
            super.goodBodyGirl.niceFigure();
        }
        if(super.greatTemperamentGirl !=null) {
            super.greatTemperamentGirl.greatTemprament();
        }
    }
}

1.4.3保证接口的纯洁性

  1. 接口要尽量小
    即要满足接口隔离原则,又要满足单一职责的原则。根据接口隔离原则拆分接口时首先必须满足单一职责的原则。
  2. 接口要高内聚
    什么是高内聚,高内聚就是提高接口、类、模块的处理嗯管理,减少对外的交互。比如:你告诉下属让你到特朗普办公室偷一个文件,下属真的给你偷过来了,这种不讲任何条件,立刻完成的任务行为就是高内聚的表现。具体到接隔离原则就是要求在接口中尽量少的公布public方法。接口是对外的承诺,承诺越少,对系统的开发越有利,变更的风险就越小,同时有利于降低成本。
  3. 接口设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也带来了结构的复杂化,开发难度增加。所以接口设计一定要注意适度。

1.5迪米特法则

1.5.1 定义

迪米特法则(Law of Demeter,LoD) 也成为最少知识原则。
一个对象应该 对其他对象有最少的了解。 一个类内部如何复杂没关系,外部调用只关心public的方法。其他一概不关心。

迪米特法则包含4中要求:

1. 只和朋友交流

朋友类定义: 一个类中出现的 成员变量、方法的输入输出参数,而出现在方法体中内的类的实例和声明不属于朋友类。

迪米特法则告诉我们一个类之和朋友类交流。但凡方法中有非朋友类声明和其实例将被视为违反了迪米特法则。(在某个类的方法中写业务时候,不能随便new对象了, 得看这个类是否是朋友类,如果不是,则违反了迪米特法则)

如下案例:
设计模式之禅笔记_第10张图片

这里的 Teacher类中的Commond 方法

public void commond(GroupLeader groupLeader) {
		List listGirls = new ArrayList() ;
	
		///初始化listGirls 
		for(int i - 0 ; i<20;i++){
		listGirls.add(new Girl()) ;
}

	groupLeader.countGirls(listGirls) ;
}

teather类中有一个朋友groupLeader 。Girl 类并不是teacher 的朋友所以不能够在teather 的 成员方法中使用 。迪米特法则要求一个类只能和朋友类交流。如果在commond 方法中 List listGirls 动态数组有了交流,就破坏了Teacher的健壮性。 方法是一个类的行为,如果一个类竟然不知道自己的行为与其他类有依赖关系,这是不允许的,严重违反了迪米特法则。

如何修改呢, 将 List listGirls声明为Teacher类的成员变量,通过构造方法传入赋值即可。
设计模式之禅笔记_第11张图片

public class Teacher {
private  List listGirls ;
public Teacher( List _listGrils){
this.listGrils = _listGrils
}

}

2. 朋友间也是有距离的

朋友之间公开的public属性和方法也不宜过多。否则修改的时候设计的面就越大,引起变更风险扩散就越大。

例子:
设计模式之禅笔记_第12张图片
InstallSoftware 安装软件类 依赖 Wizard 安装向导类

public class Wizard{
public int first() {	
	return 1 ;
}

public int second() {	
	return 2 ;
}

public int third() {	
	return 3;
}
}


public class InstallSoftware{
		public void installWizard(Wizard wizard){
			int first = wizard.first() ;
			if(first>50){
				int  second = wizard.second() ;
					if(second>50){
					   int third = wizard.third() ;
					   if(third>50) {
						System.out.prinln(“安装成功!”)
}
}
}
}

}

我们可以看到 Wizard 是 InstallSoftware 类的 朋友类,但是
Wizard的太多方法暴漏给朋友了,这样会导致一个问题,两者朋友关系太亲密耦合关系变得异常牢固。如果将Wizrd类中的 first方法 返回值由int 改为 boolean ,就需要修改 InstallSoftware类,从而把修改的风险扩散开。所以这样的耦合关系不合理,需要进行重构。

修改后的UML 图为:
设计模式之禅笔记_第13张图片

如图所示将 first() second() third() 方法由public 改为 private。
再增加一个public的installWizard()方法,在这个方法中负责前边这些方法的业务。 这样再后期修改的时候只修改当前类即可。
其代码如下:

public class Wizard{
private int first() {	
	return 1 ;
}

private int second() {	
	return 2 ;
}

private int third() {	
	return 3;
}

public void installWizard(){
	int first = wizard.first() ;
			if(first>50){
				int  second = wizard.second() ;
					if(second>50){
					   int third = wizard.third() ;
					   if(third>50) {
						System.out.prinln(“安装成功!”) :
}
}
}
}
}


public class InstallSoftware{
		public void installWizard(Wizard wizard){
			wizard.installWizard() ;
}

}

这样Wizard类只对外公布了一个public方法,若有修改first方法的需求 ,影响的也只是本类其他类不受影响,这显示了类的高内聚特性

迪米特法则要求“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、protected等访问权限。

3. 是自己的就是自己的

在实际应用中会出现这种方法: 放在本类也可以,放在其他类也没有错,拿怎么去衡量呢? 可以坚持这个原则:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,就放置到本类中。

1.5.2 小结

小结:
迪米特法则 就是让 每个对象管好自己的一亩二分地,尽可能公共独立可供其他类调用,减少只作为某一个对象私有对象。
迪米特法则要求类间解耦,但解耦是有限度的,在实际项目中需要适度的考虑这个原则,不能为了适应原则而做项目。要求类与类之间的通信尽可能在类中暴漏尽量少的方法和属性,便于维护,也就是所说的高内聚低耦合。尽量不传递串联太多的类,要不然维护起来特别麻烦。最少知道原则希望我们不要知道彼此太多,不然干涉互相伤害纠缠不清。

1.6开闭原则

1.6.1 定义

一个软件实体如 类、模块和函数应该对扩展开放,对修改关闭。

也就是说一个软件实体应该通过扩展来实现变化,而不是通过修改软件来实现变化。
软件实体包括以下部分:

  1. 项目或软件产品中按照一定的逻辑规则划分的模块。
  2. 抽象和类
  3. 方法

1.6.2 实例1:修改功能

书店销售例子:IBook定义了数据三个属性,名称 价格 和作者。小说类NovelBook 是一个具体实现的类,BookStore 包含Main函数。如下图:
设计模式之禅笔记_第14张图片

若想增加打折功能,根据开闭原则在不修改原先代码的基础上进行扩展,即创建一个OffNovelBook ,复写getPrice()方法,将打折的逻辑写在里边即可。

设计模式之禅笔记_第15张图片

代码如下:


public interface IBook {
public  String getName() ;
public String  getPrice() ;
public String  getAuthor() ;
}


public class NovelBook implements IBook {
	 private String name ;
	 private int  price ;
	 private String author ;
	 public NovelBook(String _name,int _Price,String _author){
		this.name = _name  ;
		this.price = _price ;
		this.author = _author ;
}

///省略实现IBook 接口的 好多 get 方法

}


public class BookStore {
		private final static ArrayList bookLIst = new ArrayList() ;
	//static 静态模块初始化 
	static {
		bookList.add(new NovelBook("天龙八部",3200,"金庸")) ;
		bookList.add(new NovelBook("巴黎圣母院",3200,"雨果")) ;
		bookList.add(new NovelBook("悲惨世界",3200,"雨果")) ;
		bookList.add(new NovelBook("aaa",3200,"bb")) ;
					
}
public static void main(String Args[]) {

//for (IBook book: bookList){
	book.getAuthor() ;
	book.getName() ;
	book.getPrice() ;
}

}
}

1.6.2 实例2

如果增加计算机类图书,并且计算机类图书新增了一个方法就是获得该书籍的范围
如图所示:
设计模式之禅笔记_第16张图片

新增一个IComputerBook 接口及其 实现类
代码如下:

public  interface IComputerBook extends  IBook{
	public String getScope() ;
}


public class ComputerBook implements IComputerBook{
	private String name  ;
	private String scope ;
	private String author;
	private int price ;
public ComputerBook(String _name,String _scope,String _author,String _price){
	this.name = _name ;
	this.scope = _scope;
	this.author = _author;
	this.price = _price ;
}

// 省略一堆 实现的方法
	
}

你可能感兴趣的:(设计模式之禅笔记)