设计模式七大原则
七大原则事实上是程序员在开发的时候需要遵守的原则,它们也是各大设计模式的基础和由来
核心思想:
1.找出应用中可能需要变化之处,把它独立出来,不要和那些不需要变化的代码混合在一起。
2.针对接口编程,而不是针对实现编程
3.为了交互对象之间的松耦合设计而努力
研究七大原则需要带着这几个问题来研究:
1. 这个原则是怎么产生的
2. 他是用来干嘛的
3. 他有什么作用
4. 怎么用
1.单一原则
- 定义:就一个类而言,应该仅有一个引起它变化的原因。
- 产生的原因:如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化的时候,设计会发生意想不到的破坏。意思是说:现在要写一个狗狗的程序(狗狗会跑,会叫,会吃饭,会拍拍手,会握手等等技能)那么一般的程序员就会将这些所有的功能都写在同一个类里面,这样就会造成这个类很臃肿,当你想要修改狗狗会跑的功能的时候就可能会影响到其他的功能,并且如果给狗狗增加或者修改的技能的时候就会很难维护,那么这个时候单一原则就产生了。
- 作用:易扩展,易维护,易复用,灵活多样。
- 怎么使用:在我们设计或者实现一个功能的时候,应该尽可能的去发现或者考虑到这个功能以后会新增什么功能,有什么扩展的可能性,应该尽可能的去拆分他们。(在逻辑足够简单的时候就可以违反单一原则)
- 实例:在你实现一个业务操作时,如果需要操作两个或以上的表那么你就要将其拆分成多个方法来实现,每个方法对应着相应的逻辑操作以及操作对应的表.并且尽可能的将可拆分的逻辑单独出一个个方法里面 。例如:一个购买A商品的接口;需要做的是,要对用户的余额进行操作,要新增订单记录,这时你就会一个接口操作两个表,那么这时你就可以将其拆分为两个方法,A1方法对用户进行余额操作,A2方法对订单表进行操作,如果还需要验证支付密码或者其他的计算逻辑就要再拆分两个方法
2.接口隔离原则
- 定义:客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
- 产生的原因:现有一个接口它有五个方法,A类需要使用这个接口的1.3.4方法,B类需要使用1.2.5方法,但是当A/B类实现这个接口的话就需要实现这五个方法。这样问题就来了,类A就会多出2.5两个方法未使用,类B就会多出3.4两个方法未使用,这样就会做了一下无用功,这时接口隔离就产生了。
- 作用:减少接口过于臃肿的设计。
- 怎么使用:基于上面的例子,我们可以将接口拆分为接口1与接口2以及接口3这三个接口,然后接口1里面有方法1,接口二里面有方法3.4,接口三上面有方法2.5,最后A类需要实现接口1与接口2,而B类则需要实现接口1以及接口3,这样就会达到接口隔离原则的目的了。
3.依赖倒转原则
- 定义:高层模块不应该依赖底层模块。两个都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。
- 产生的原因:小狗吃狗粮;我们要实现这个功能的时候就要有一个puppy类以及一个eatFood类,但是狗狗不止可以吃狗粮还可以吃骨头,吃肉等等,那么如果要实现这些功能就需要重写eatFood类或者增加类,这样就会不灵活并且增加了类与类之间的耦合性。
class EatFood{
public String eat(){
return "吃狗粮……";
}
}
class Puppy{
public void puppyEatFoot(EatFood eatFoot){
System.out.println("小狗开始吃东西了!");
System.out.println(eatFoot.eat());
}
}
public class Client{
public static void main(String[] args){
Puppy puppy= new Puppy();
puppy.puppyEatFoot(new EatFood());
}
}
- 作用:减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
- 怎么使用:有三种实现方式
1.接口传递
class interface EatFood{
public void String eat();
}
public void EatMeat implements EatFood {
public void String eat(){
System.out.println("吃肉。。。。。");
}
}
public void EatBones implements EatFood {
public void String eat(){
System.out.println("吃骨头。。。。。");
}
}
class Puppy{
public void puppyEatFoot(EatFood eatFoot){
System.out.println("小狗开始吃东西了!");
System.out.println(eatFoot.eat());
}
}
public class Client{
public static void main(String[] args){
Puppy puppy= new Puppy();
puppy.puppyEatFoot(new EatMeat ());
puppy.puppyEatFoot(new EatBones ());
}
}
2.构造方法传递
class interface EatFood{
public void String eat();
}
public void EatMeat implements EatFood {
public void String eat(){
System.out.println("吃肉。。。。。");
}
}
public void EatBones implements EatFood {
public void String eat(){
System.out.println("吃骨头。。。。。");
}
}
class Puppy{
private EatFood eatFood;
public Puppy(EatFood eatFood){
System.out.println("小狗开始吃东西了!");
this.eatFood=eatFood;
}
}
public class Client{
public static void main(String[] args){
Puppy puppy= new Puppy(new EatMeat ());
Puppy puppy= new Puppy(new EatBones ());
}
}
3.setter方法传递
class interface EatFood{
public void String eat();
}
public void EatMeat implements EatFood {
public void String eat(){
System.out.println("吃肉。。。。。");
}
}
public void EatBones implements EatFood {
public void String eat(){
System.out.println("吃骨头。。。。。");
}
}
class Puppy{
private EatFood eatFood;
public void setFood(EatFood eatFood){
System.out.println("小狗开始吃东西了!");
this.eatFood=eatFood;
}
}
public class Client{
public static void main(String[] args){
Puppy puppy= new Puppy();
puppy.setFood(new EatMeat ());
puppy.setFood(new EatBones ());
}
}
注意:
1、低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。
2、变量的声明类型尽量是抽象类或接口
3、继承时遵循里氏替换原则
- 实例:就像是spring的三层架构(control/service/impl);现在有很多人的写法是直接将逻辑写在service里面,从而减去impl,这样就会违反了依赖倒转原则,我们应该在control里依赖抽象接口service然后再在impl里面实现接口以及写逻辑;依赖倒转原则的中心思想是面向接口编程.
4.里氏替换原则
- 定义:子类型必须能够替换掉他们的父类型。
- 产生的原因:现有B/C两个子类继承它的父类A,当我们修改A类的方法的话会有可能影响到它的子类B/C,所以产生了里氏原则。如果你非要违反这个原则,那么你可以采用:他们都继承一个更加基础的类,将原有的继承关系去掉,采用依赖、聚合、
- 作用:侵入性、不够灵活、高耦合。
- 注意:里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
它包含以下4层含义:
1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2.子类中可以增加自己特有的方法。
3.当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
5.开闭原则
- 定义:软件实体、类、模块、函数等,应该可以扩展(对提供方),但是不能修改(对使用方)。
- 产生的原因:在我们设计或者实现一个功能的时候无可避免的是,当在有新需求的时候我们总是去修改使用方的代码,这样的话就会有可能造成代码的耦合性增加,或者会影响到这个方法的另一个调用方。而开闭原则是指:在你设计的时候时刻要考虑,尽量要让这个类足够好,写了就不要去修改了,如果有新需求过来那么增加一些类就行了,原来的代码能不改就不改。
- 作用:提高可复用性,易于扩展和维护。
- 使用:当然,无论是多么"封闭"的代码,都会存在一些无法对封闭之外的变化。既然不可能完全封闭那么设计人员就要对他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出最有可能发生变化的种类,然后使用抽象来隔离这些变化。(这里的猜测就要靠经验了)面对新需求,我们应该是增加新的代码,而不是修改已有的代码
6.迪米特原则
- 定义:一个对象应该对其他对象有最少的了解。只和直接朋友交流。
什么是朋友?
每个对象必然都会与其他对象有耦合关系,两个对象之间的耦合就是朋友关系。这种关系的类型有组合,聚合,依赖。
什么是朋友类?
就是出现在成员变量,或者方法的输入输出参数中的类才叫朋友类。 - 作用:降低类之间的耦合
- 使用:
1.只和朋友交流
一个方法尽量不引入类中不存在的对象
2.朋友间保持距离
尽量少使用public公开信息
3.自己的就是自己的
如果一个方法既不产生类间关系,又不对本类产生负面影响,那就放在本类中
4.谨慎使用Serializable
7.合成复用原则
- 定义:尽量使用合成/聚合的方式,而不是使用继承。