SOLID面向对象模式浅析

对象是对客观事物的抽象,类是对对象的抽象。对象是类的实例,类是对象的模版。

1.  单一责任原则(SRP)

SingleResponsibilityPrinciple2_71060858

它的意思是:“如果你可以在一个设备中实现所有的功能,你却不能这样做”。为什么呢?因为从长远来看它增加了很多的可管理性问题。

 

         一个类有且只有一个职责。这并不意味着一个类就必须只有一个方法,但他们的目的必须          是一样的。如果有多个目的,那么你就必须拆分这个类,为什么呢?

 

  • 每个职责都是轴向变化;
  • 如果类包含多个职责,代码会变得耦合;

      

        违反SRP原则的类层次结构

        这里,Rectangle 类干了下面两件事:

  • 计算矩形面积;
  • 在界面上绘制矩形;

        而且,有两个程序使用了 Rectangle 类:

  • 计算几何应用程序用这个类计算面积;
  • 图形程序用这个类在界面上绘制矩形;

        这违反了SRP原则(单一职责原则)

       后果就是,我在计算图形面积的时候,也必须去引用GUI资源。

    可以按职责拆成两个类:

  • Rectangle:这个类定义 Area() 方法;
  • RectangleUI:这个把 Rectangle 类继承过来,定义 Draw() 方法。

总结:SPR 就是把东西分到不能再分了,再集中化管理和复用。方法也得分开,一个方法干一个活。 

2.   开闭原则。(OCP)

OpenClosedPrinciple2_2C596E17

当你穿外套的时候,不需要做开胸手术。

“模块应该对扩展开放,对更改关闭“  1. ”对扩展开放“,当需求改变时,我们可以最模块进行扩展,使其具有满足那些改变的新行为,使软件具有适应性和灵活性。2. ”对更改关闭“,当我们需要扩展新功能时,应编写新的代码,不应该修改原来的代码。

对于OCP原则来说,抽象是关键。例如:如果多个client依赖于一个具体的server,当server需要扩展新功能时,将导致所有的client都可能改动。解决方法就是抽象出来一个server,使client依赖这个抽象的server,仅说明哪些不易改变的一组服务,使其子类提供具体的实现和扩展,这样具体的实现和扩展的修改就不会影响到client。

对于OCP的另外一种理解是”封装可变性原则“(EVP)。其核心思想是”找到系统中的可变元素,将其封装起来“。EVP有两个要点:

1.  可变性不应散落在代码的各个角落,而应封装到一个类中。

2. 一种可变性不应该和另一种可变性混合在一起。

你允许什么发生改变,但不能让这个改变导致重新设计。OCP是面向对象设计的核心所在,但实际中滥用OCP也是错误的,正确的做法是仅对程序中呈现出频繁变化的部分进行抽象和封装

175736_RlPz_1384334.png

3. 里氏替换原则。(LSP)

LiskovSubtitutionPrinciple_52BB5162

子类型必须能够替换它们的父类.

思想:继承必须确保父类所拥有的性质在子类中仍然成立,当一个子类的实例能够替换任何其父类的实例时,它们之间才具有is-a-kind-of-A关系.只有当一个子类(派生类)可以替换掉其父类(基类)而软件功能不受影响时,基类才能被复用,派生类也才能在基类的基础上增加新的行为. 

其本质是在同一个继承体系中的对象应该有共同的行为特征. 

例如:企鹅不属于鸟类,因为企鹅不会飞,所以企鹅不能继承鸟类。

03185033_EPPB.png

   Ostrich 都不应该继承 Bird 类,而应该从 Bird 中分出一个不会飞的类,由 Ostrich 继承。

   

  • 如果不遵循 LSP原则,类继承就会混乱。如果子类实例被作为参数传递给方法,后果难以预测。
  • 如果不遵循 LSP原则,基于父类编写的单元测试代码将无法成功运行子类。

这个原则是对继承的一个约束,也就是说,继承中子类严格满足"is a "的关系.这里我个人有深刻的体会.尤其是在看别人的UML图的时候. 
对你帮助很大.当你看到一个继承的时候.要习惯性的把他的父类和子类看成一个整体.这样会有助于你去理解各个类之间的关系.因为根据 
里氏代换原则.父类出现的地方子类也可以出现.

比如我们再看工厂模式的时候,你看到有的书上简单工厂类关联着运算父类.有的书上是关联着运算类的子类.其实仔细想想他们都没有错, 
父类和子类的关联都是一样的,父类能出现的地方,子类就可以出现. 

4. 接口分离原则(ISP)。

InterfaceSegregationPrinciple_60216468

提供尽可能”小“的,单独的接口,而不是提供一个大的接口。

思想:多个和客户相关的接口要好于一个通用接口.如果一个类有几个使用者,与其让这个类再如所有使用这需要使用的所有方法,还不如为每个使用者创建一个特定接口,并让该类分别实现这些接口.。不要让接口中有和该接口功能无关的多余的方法。

03185033_FwqG.png

如上图所示,类A依赖接口I中方法1\方法2\方法3, 类B是对类A依赖的实现, 类C依赖于接口I中的方法方法1\方法4\方法5, 类D是对类C依赖的实现.对于类B和类D中都存在用不到的方法, 但是由于实现了接口I,所以需要实现这些不用的方法。可以发现接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用,实现类中都必须去实现这些方法,如类B中方法4和方法5,类D中方法2和方法3,显然这样的设计不适用..

03185033_yjvv.png

接口隔离原则即尽量细化接口,接口中的方法尽量少,为各个类建立专用的接口,而不是试图去建立一个庞大而臃肿的接口提供所有依赖于他的类去调用.

说到这里,很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。

其一,单一职责原则原注重的是职责,而接口隔离原则注重对接口依赖的隔离.

其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建.

5.   依赖倒置原则。(DIP)

DependencyInversionPrinciple_0278F9E2

思想:高层模块不应该依赖低层模块,二者都应该依赖于抽象.

换句话说:要依赖于抽象,而不要依赖于具体实现;高层是功能和策略,低层是具体实现;编程程序语言就是需要针对接口编程而不要针对实现编程.

        就像我们造汽车,我们不能根据某台汽车去造轮子,我们应该按照一种标准去造,如果按照某台汽车去造轮子,轮子坏了,难道要换新车?

例如:

03185033_bMT0.png

现在司机eastmount不仅要开奔驰车,还要开宝马车,又该怎么实现呢?自定义宝马汽车类BMW,但是不能开,因为eastmount没有开宝马车的方法,出现的问题就是:

司机类和奔驰车类之间是一个紧耦合关系,这导致了系统的可维护性大大降低,增加新的车类如宝马BWM汽车就需要修改司机类drive()方法,这不是稳定性而是易变的.被依赖者的变化竟然让依赖者来承担修改的代价,所以可以通过依赖倒置原则实现.

如下图所示,通过建立两个接口IDriver和ICar,分别定义了司机的职能就是驾驶汽车,通过drive()方法实现;汽车的职能就是运行,通过run()方法实现.

03185033_8mOo.png

总结:

1. 单一指责原则规范了类和方法的功能必须单一。

即:将不同的功能隔离开来,不要都混合到一个类中。

2. 开闭原则要求(类,模块,函数等)应该对扩展开放,对修改关闭。

即,如果遇到需求变化,要通过添加新的类来实现,而不是修改现有的代码。这一点也符合单一职责原则。

3. 里氏替换原则规定了继承。子类可以完全替换父类。

4. 接口隔离原则要求接口不要臃肿。

添加新功能时,要增加一个新接口,而不是修改已有的接口,禁止出现“胖接口”。符合单一职责原则和开放封闭原则。

5. 依赖倒置原则规定要面向接口编程。

具体依赖于抽象,而非抽象依赖与具体。即,要把不同子类的相同功能抽象出来,依赖与这个抽象,而不是依赖于具体的子类

我们讨论了5个设计原则,最重要的是OCP,其他四个都附属于开闭原则,违背其他四个原则都直接或间接违背OCP。

solid原则的目的就是为了提取可变的部分封装,提高代码可复用性。

其他两个原则:

5、迪米特法则(最少知道原则)(Demeter Principle)

就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

class Girl{
    //女生
}

老师要班长点名

class Teacher{

	//让班长点名
	public function Commond($groupLeader){

		//老师却和女生有关系了。不合理
		
		$girls = array();

        //添加女生都放老师类里面了。如果我要再加一个,就要修改老师类,耦合度太高,代码重用太低
		for($i = 0; $i < 20; $i++){
			
			$girls[] = new Girl();
		
		}

		return $groupLeader -> countGirls($girls);
	
	}

}


class GroupLeader{
	//班长
	
	public function countGirls($girls){
		return sizeof($girls);
	}

}

迪米特原则:

class Teacher{

	public function Commond($groupLeader){
		//只是告诉班长,你点一下名字。不用去和女生有关系
		return $groupLeader -> countGirls();
	
	}

}


class GroupLeader{

	private $_girls = array();

	//班长
	public function countGirls(){
		return sizeof($this ->_girls);
	}

	public function addGirl($girl){
	    //可以独立添加女孩,而不用修改类
		$this -> _girls = $girl;
	}

}

$groupLeader = new GroupLeader();

//如果换成了男孩,也不用修改班长和老师类
$groupLeader -> addGirl(new Girl());
$groupLeader -> addGirl(new Girl());
$groupLeader -> addGirl(new Girl());


$teacher = new Teacher();
//如果换了班长也不用修改老师类
$teacher -> Commond(new $groupLeader());

//修改后代码的可重用性可见

用迪米特原则的设计模式:外观模式和中介者模式

6、合成复用原则(Composite Reuse Principle)

原则是尽量首先使用合成/聚合的方式,而不是使用继承

 

单一职责原则告诉我们实现类要职责单一;

里氏替换原则告诉我们不要破坏继承体系;

依赖倒置原则告诉我们要面向接口编程;

接口隔离原则告诉我们在设计接口的时候要精简单一;

迪米特法则告诉我们要降低耦合(类与类之间的调用关系)。

而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

转载于:https://my.oschina.net/shyl/blog/525465

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值