面向对象七大设计原则

面向对象的设计原则概述

这些原则蕴含在很多设计模式中,它们是从许多设计方案中总结出的指导性原则

在设计模式的学习中,大家经常会看到诸如“XXX模式符合XXX原则”、“XXX模式违反了XXX原则”这样的语句

面向对象七大设计原则

开闭原则

对修改关闭,对扩展开放。一切都是为了保证代码的扩展性和复用性。而开闭原则是基础要求

白话文来说就是:软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能(Functions)等等,应该在不修改现有代码的基础上,去扩展新功能,开闭原则中原有“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;开闭原则中“闭”,是指对于代码的修改是封闭的,即不应该修改原有的代码。

当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

  • 水果接口

public interface Fruit{
    Double getFriutPrice(String fruitName);
    Double getFruitSize(String fruitName);
}
  • 水果接口的实现类 ---> 橘子

public class Peach implments Fruit{
    private String productArea;
    private Double price;
    private Double size;
    ... 构造方法 set get ...
}
  • 今年橘子想做促销,把橘子的价格调低销售

    • 如果我们直接去修改橘子类的的价格是不适合的

    • 我们可以用一个新的促销类来继承Peach

    • 重写其price方法,这样就保证了原来调用Peach的地方不受影响。

    • 无须修改现有类库的源代码 ,实现了价格的打折处理

public class SalesPeach extends Peach{
    private static final Double sales_Factor = 0.75d;
    public Peach(Double price,Double size,String productArea) {
        super(price,size,productArea);
    }
    //促销打7.5折
    public Double getPrice(){
        return super.getPrice() * sales_Factor;
    }
}

单一职责原则

一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。 它用于控制类的粒度大小

接口隔离原则

使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口

当一个接口太大时,我们需要将它分割成一些更细小的接口,一个客户端不应该依赖它用不到的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。

  • 动物接口

public interface Animal {
    void eat();
    void fly();
    void swim();
}
  • 动物接口实现类 --> 鱼类

public class Fish implements Animal {
    void eat(){
        System.out.print("鱼儿进食");
    }
    void fly(){}
    void swim(){
        System.out.print("鱼儿游泳");
    } 
}
  • 动物接口实现类 --> 鸟类

public class Bird implements Animal { 
    void eat(){
        System.out.print("鸟儿进食");
    }
    void fly(){
        System.out.print("鸟儿飞翔");
    }
    void swim(){}
}
  • 动物接口实现类 --> 猪类

public class Pig implements Animal {
    void eat(){
        System.out.print("猪儿进食");
    }
    void fly(){}
    void swim(){}
}

上面这种接口和类的关系就看得出来,重接口带来的问题很明显

  • 鱼不会飞

  • 鸟不会游泳

  • 猪不会飞也不会游泳

但是,由于他们实现的接口是一个重接口,包含了动物的很多行为方法,所以每一个动物的实现者都要重写很多自己根本不需要的行为方法,即使为空

此时改进的方法就是,将重借口的功能拆分,根据更高的复用性作为一个标准来拆分,如果以上图为列

  • 动物重接口拆分为三个细接口,分别是 eat 、fly、swim

  • 鱼想要吃饭和游泳,就去实现eat 和swim,而不需要去实现fly

  • 同理,猪只会吃饭,也就只需要实现eat即可

依赖倒置原则

依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。

  • 大学生选课,该学生特别热爱java课程,所以就选了java课程,当他还想选择大数据课程的时候,我们就需要在Student中增加一个方法 void chooseBidDataCourse();这样是非常不稳定的,这样的修改风险也较高。

  • 改造重构

public interface Course {
    void choose();
}
  • Java课程

public class JavaCourse implements Course {
    void choose() {
        System.out.println("选择了java课程");
    }
}
  • 大数据课程

public class BigDataCourse implements Course {
    void choose() {
        System.out.println("选择了大数据课程。");
    }
}
  • 大学生选课

    • 你想选什么课,都只需要调用chooseCource方法即可

    • 方法的参数是一个层次很高的接口:Course

    • 只要是Course的实现子类或者孙子类都可

public class Student{
    void chooseCource(Course course) {
        course.choose();
    }
}

里式替换原则

如何去编写继承类的代码,子类不要去覆盖父类已经实现的方法。(抽象模板方法)

程序中可以使用父类的地方,一定也可以使用子类,即子类可以替换掉父类而不影响程序的正常运行。

里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在 程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对 象。

  • 父类:BaseClass

  • 子类:SubClass

  • 如果一个方法void test1(BaseClass base)

    • 该test1方法也可以接受SubClass 的入参

  • 如果一个方法void test2(SubClass sub)

    • 该test2方法不可以接受BaseClass 的入参,层次等级不够

迪米特法则

最少认知原则,只和你的朋友交谈,不要和陌生人说话。

只和你的朋友交谈,不要和陌生人说话,对于一个对象,其朋友包括以下几类:除此之外统一称为陌生人

  • 当前对象本身(this);

  • 以参数形式传入到当前对象方法中的对象;

  • 当前对象的成员对象;

  • 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;

  • 当前对象所创建的对象

迪米特法则要求我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。

比如:项目经理想知道公司产品的测试结果

  • 项目经理直接去访问与他没有直接关系的测试人员,关系复杂,人员依赖调用复杂,耗时1天

  • 项目经理向测试经理要测试结果,测试经理轻车熟路的调动人员分析得到测试结果,耗时2小时

明显看出,直接问测试经理要结果,省时省力

 

合成复用原则

能用组合关系的情况下,不要使用继承关系。就比如说,如果你想拥有某个对象的功能,不要直接继承它,而是将它作为我的成员变量去使用 ,使用关联复用来取代继承复用

  • 获取数据库连接工具类

public class DBConnection{
    public  String getConnection(){
        System.out.println("使用MYSQL数据库创建连接");
        return "Mysql";
    }
}
  • Dao完成数据库操作

public class UserDAO extends DBConnection {
 
    public void queryUser() {
        String connection = super.getConnection();
        System.out.println("使用数据库连接进行查询" + connection);
    }
}
  • 加入此时我们要更换Mysql为Oracle了怎么办啊

    • 难道要在DBConnection中新增连接Oracle的方法

    • 然后去所有Dao中更改以往的代码,获取Oracle的的连接吗

    • 无论修改DBConnection还是修改UserDAO都是违背开闭原则的

  • 更改如下

public class OracleConnection extends DBConnection {
    public  String getConnection(){
        System.out.println("使用Oralce数据库创建连接");
        return "Oracle";
    }
}
public class UserDAO  {
    public void queryUser(DBConnection connection ) {
        String connection = connection.getConnection();
        System.out.println("使用数据库连接进行查询" + connection);
    }
}
  • 根据里氏代换原则,OracleConnection作为子类对象可以覆盖父类DBConnection对象

  • 如果以后,我们还想扩展其他的连接,只需要再写一个子类实现DBConnection,然后注入到方法中即可

  • 灵活地增加新的数据库连接方式

.

posted @ 2021-03-05 14:23  鞋破露脚尖儿  阅读(138)  评论(0编辑  收藏  举报