设计模式的七大原则

一、设计模式的目的

1、代码重用性

2、可读性

3、可扩展性

4、可靠性

5、使程序呈现高内聚、低耦合的特性

二、设计模式的七大原则

  1. 单一职责原则
  2. 接口隔离原则
  3. 依赖倒置原则
  4. 里氏替换原则
  5. 开闭原则
  6. 迪米特法则
  7. 合成复用法原则

1、单一职责

设计思想:一个类或者一个方法只负责一项职责,尽量做到类的只有一个行为原因引起变化。

问题:假如有类Class1完成职责T1,T2,当职责T1或T2有变更需要修改时,有可能影响到该类的另外一个职责正常工作。

优点:类的复杂度降低、可读性提高、可维护性提高、扩展性提高、降低了变更引起的风险。

实际项目中考虑的点:根据具体的业务去划分类或者方法职责问题,怎么拆分,拆分粒度的把控,怎么做到重复利用,扩展性的考虑,怎么避免变更引起的风险等。

2、接口隔离原则

设计思想:类间的依赖关系应该建立在最小的接口上

问题:类A通过接口interface依赖类B,类C通过接口interface依赖类D,如果接口interface对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

优点:提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情,为依赖接口的类定制服务。只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。

实际项目中考虑的点:不要建立庞大的接口,建议抽取公共base接口,然后针对不同类的职责定义自己的接口,从而实现公共接口和属于类自己定义的接口,多接口实现。

3、依赖倒置原则

设计思想:高层模块不应该依赖底层模块,二者都该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象(高层模块就是调用端,低层模块就是具体实现类。抽象就是指接口或抽象类。细节就是实现类)

本质:依赖倒置原则的本质就是通过抽象(接口或抽象类)使个各类或模块的实现彼此独立,互不影响,实现模块间的松耦合

问题:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

问题解决方案:将类A修改为依赖接口interface,类B和类C各自实现接口interface,类A通过接口interface间接与类B或者类C发生联系,则会大大降低修改类A的几率

优点:依赖倒置的好处在小型项目中很难体现出来。但在大中型项目中可以减少需求变化引起的工作量。使并行开发更友好。

实际项目中考虑的点:在spring中模块的划分就是遵循了依赖倒置的原则,将类依赖关系变成接口,接口的实现独立,如果需要更改实现,只需要改接口的实现类就可以。

实例:

/**
 * 不考虑设计原则下的写法
 */
public class Persion {
    
    /**
     * 实例:人吃水果
     * 开始想吃苹果就要去调吃苹果的类方法
     * 后面想吃桔子就要去调吃桔子的类方法
     * 这时候先相关改变就要变化eating方法的参数,显然每吃一次不同的水果都要去改变eating方法,违反了依赖倒置原则
     * @param apple
     */
    public void eating(Apple apple) {
        System.out.println(apple.eat());
    }

    static class Apple{

        public String eat() {
            return "苹果";
        }
    }

    static class Orange{
        public String eat() {
            return "桔子";
        }
    }

    public static void main(String[] args) {

        Persion designPrinciples = new Persion();
        designPrinciples.eating(new Apple());
    }
}

/**
 * 优化后,让不同的水果都去实现 一个公共的接口,eating参数传抽象的接口,这样想吃不同的水果就不需要去更改eating方法参数了
 */
public class Persion {

     public void eating(Shuiguo shuiguo) {
        System.out.println(shuiguo.eat());
    }

    interface Shuiguo {
        String eat();
    }

    static class Apple implements Shuiguo{

        @Override
        public String eat() {
            return "苹果";
        }
    }

    static class Orange implements Shuiguo{

        @Override
        public String eat() {
            return "桔子";
        }
    }
}


 

4、里氏替换原则

设计思想:里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

问题:如果程序违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误。这时其修正方法是:取消原来的继承关系,重新设计它们之间的关系。

优点:代码共享,减少创建类的工作量、提高代码的重用性、子类可以形似父类,又异于父类、提高父类的扩展性,实现父类的方法即可随意而为.

实际项目中考虑的点:

1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法(不能把Y基因变成了Z,O(∩_∩)O哈哈~)。

2、子类中可以增加自己特有的方法,但需要在父类中声明(要跟到老婆走,是不先要给父亲说些呢?)。

3、当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松,这个

从参数个数上考虑。

4、当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更加严格,如父类要

求返回List,那么子类就应该返回List的实现ArrayList,父类是采用泛型,那么子类则不能采用泛型,而是具体的返

回。

实例:

package yuanze;
 
/**
 * 将父类声明为抽象类
 * 
 * @author Administrator
 *
 */
public abstract class Super {
 
	/**
	 * 定义一个父类无须实现的抽象方法
	 */
	public abstract void breathe();
 
	/**
	 * 定义父类需要实现的方法
	 */
	public void eat() {
		System.out.println("我吃面");
	}
 
}

package yuanze;
 
/**
 * 定义子类继承父类
 * 
 * @author Administrator
 *
 */
public class Child extends Super {
	/**
	 * 子类扩展的方法,但是在父类中有声明
	 */
	@Override
	public void breathe() {
		// TODO Auto-generated method stub
		System.out.println("呼吸");
	}
 
}

//客户端调用
package yuanze;
 
public class Test {
 
	public static void main(String[] args) {
 
		Super c = new Child(); // 由子类的实例替代父类的实例
		c.eat();
	}
}

 

5、开闭原则

设计思想:对拓展开放,对修改关闭:比如当某个业务增加,不是在原类增加方法,而是增加原类的实现类

问题:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。

优点:可以提高代码的可复用性、粒度越小,被复用的可能性就越大;在面向对象的程序设计中,根据原子和抽象编程可以提高代码的可复用性、可以提高软件的可维护性、遵守开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护。

实际项目中考虑的点:从接口关系中去看: 要建立共同的接口,创建不同的业务类去实现公共的接口,从而达到业务类的隔离互不影响。从继承关系中去看:继承需要扩展类的并重写需要的方法,从而达到不影响原先的的方法。

实例:

//书籍接口
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;
   }
 
   public String getAutor(){
     return this.author;
   }
 
   public String getName(){
     return this.name;
   }  
 
   public int getPrice(){
     return this.price;
   } 
}

//客户端调用
public class Client{
   public static void main(Strings[] args){
     IBook novel = new NovelBook("笑傲江湖",100,"金庸");
     System.out.println("书籍名字:"+novel.getName()+"书籍作者:"+novel.getAuthor()+"书籍价格:"+novel.getPrice());
   }
 
}

现在来了新需求:项目投产生,书籍正常销售,但是我们经常因为各种原因,要打折来销售书籍,这是一个变化,我们要如何应对这样一个需求变化呢

  • 修改接口 
    在IBook接口中,增加一个方法getOffPrice(),专门用于进行打折处理,所有的实现类实现此方法。但是这样的一个修改方式,实现类NovelBook要修改,同时IBook接口应该是稳定且可靠,不应该经常发生改变,否则接口作为契约的作用就失去了。因此,此方案否定。

  • 修改实现类 
    修改NovelBook类的方法,直接在getPrice()方法中实现打折处理。此方法是有问题的,例如我们如果getPrice()方法中只需要读取书籍的打折前的价格呢?这不是有问题吗?当然我们也可以再增加getOffPrice()方法,这也是可以实现其需求,但是这就有二个读取价格的方法,因此,该方案也不是一个最优方案。

  • 通过扩展实现变化 
    我们可以增加一个子类OffNovelBook,覆写getPrice方法。此方法修改少,对现有的代码没有影响,风险少,是个好办法。

方案:增加一个打折类去继承具体的书籍类,并覆盖重写获取价格的方法

public class OffNovelBook extends NovelBook{
 
   public OffNovelBook(String name,int price,String author){
      super(name,price,author);
   }
 
   //覆写价格方法,当价格大于40,就打8析,其他价格就打9析
   public int getPrice(){
     if(this.price > 40){
        return this.price * 0.8;
     }else{
        return this.price * 0.9;
     }     
   } 
}

现在打折销售开发完成了,我们只是增加了一个OffNovelBook类,我们修改的代码都是高层次的模块,没有修改底层模块,代码改变量少,可以有效的防止风险的扩散。

6、迪米特法则

设计思想:迪米特法则又叫最少知道原则,类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。于是就提出了迪米特法则。通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。

问题:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大

优点:迪米特法则可降低系统的耦合度,使类与类之间保持较低的耦合关系。

实际项目中考虑的点:

实例:

现在在线教育网的老板要知道在线教育网有多少课程,他直接向团队领导下命令即可,再由团队领导查询有多少课程。
//**************违反迪米特法则的写法**********************//
//课程类:Crourse类
public class Course{
}

//Boss类
public class Boss{
  public void commandCheckNumber(TeamLeader teamLeader){
   List<Course> courseList = new ArrayList<Course>();
        for(int i = 0 ;i < 20;i++){
            courseList.add(new Course());
        }
        teamLeader.checkNumberOfCourses(courseList);
  }
}

//TeamLeader类
public class TeamLeader {
    public void checkNumberOfCourses(List<Course> courseList){
    System.out.println("在线课程的数量是:"+courseList.size());
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        Boss boss = new Boss();
        TeamLeader teamLeader = new TeamLeader();
        boss.commandCheckNumber(teamLeader);

    }
}

这样做没有啥问题,但违反了迪米特法则,Boss类不需要和Course类发生交流,他只需和TeamLeader类发生交流即可。

Course类应由TeamLeader类创建,不应由Boss类创建.所以我们改进为如下代码

//Boss类:
public class Boss {

    public void commandCheckNumber(TeamLeader teamLeader){
        teamLeader.checkNumberOfCourses();
    }
}

//Course类
public class Course {
}

//TeamLeader类
public class TeamLeader {
    public void checkNumberOfCourses(){
        List<Course> courseList = new ArrayList<Course>();
        for(int i = 0 ;i < 20;i++){
            courseList.add(new Course());
        }
        System.out.println("在线课程的数量是:"+courseList.size());
    }

}

//Test测试类
public class Test {
    public static void main(String[] args) {
        Boss boss = new Boss();
        TeamLeader teamLeader = new TeamLeader();
        boss.commandCheckNumber(teamLeader);
    }
}

7、合成复用原则

设计思想:在系统中尽量多使用组合或聚合关联关系,少使用或不使用继承关系。

问题:通常类的复用分为继承复用和合成复用两种,继承复用虽然有简单和易实现的优点,但它也存在以下缺点。 1.继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。 2.子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。 3.它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

优点:采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点。 1.它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。 2.新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。 3.复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

实际项目中考虑的点:如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。

实例:

汽车分类管理程序。

分析:汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。图 1 所示是用继淨:关系实现的汽车分类的类图。

用继承关系实现的汽车分类的类图:

从图 1 可以看出用继承关系实现会产生很多子类,而且增加新的“动力源”或者增加新的“颜色”都要修改源代码,这违背了开闭原则,显然不可取。但如果改用组合关系实现就能很好地解决以上问题,其类图如图 2 所示。

用组合关系实现的汽车分类的类图:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值