面向对象七大设计原则,看了必会(代码详细版)

咱们首先了解一个单词

solid

这个单词和咱们今天要讲述的七大原则有什么关系?

咱们首先要了解七大原则都有什么?

  1. 单一职责原则 (Single Responsibility Principle)
  2. 开放-关闭原则 (Open-Closed Principle)
  3. 里氏替换原则 (Liskov Substitution Principle)
  4. 接口隔离原则 (Interface Segregation Principle)
  5. 依赖倒转原则 (Dependence Inversion Principle)
  6. 迪米特法则(Law Of Demeter)
  7. 组合/聚合复用原则 (Composite/Aggregate Reuse Principle)

咱们来进行详细的讲解。

1、单一职责原则

定义:
单一职责原则(Single Responsibility Principle,SRP)又称单一功能原则,由罗伯特·C.马丁(Robert C. Martin)于《敏捷软件开发:原则、模式和实践》一书中提出的。这里的职责是指类变化的原因,单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分(There should never be more than one reason for a class to change)。
优点:

单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。如果遵循单一职责原则将有以下优点。
1、降低类的复杂度。一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多。
2、提高类的可读性。复杂性降低,自然其可读性会提高。
3、提高系统的可维护性。可读性提高,那自然更容易维护了。
4、变更引起的风险降低。变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可以显著降低对其他功能的影响。

类层面:
错误示范:

public class SingleResponsibility1 {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.run("汽车");
        vehicle.run("飞机");
        vehicle.run("轮船");
    }
}

class Vehicle {
    public void run(String vehicle) {
        System.out.println(vehicle + " 在公路运行...");
    }
}
汽车 在公路运行...
飞机 在公路运行...
轮船 在公路运行...

正确示范:

public class SingleResponsibility2 {
    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
        roadVehicle.run("汽车");

        AirVehicle airVehicle = new AirVehicle();
        airVehicle.run("飞机");

        WaterVehicle waterVehicle = new WaterVehicle();
        waterVehicle.run("轮船");
    }
}

class RoadVehicle {
    public void run(String vehicle) {
        System.out.println(vehicle + " 在公路运行...");
    }
}

class AirVehicle {
    public void run(String vehicle) {
        System.out.println(vehicle + " 在天空运行...");
    }
}

class WaterVehicle {
    public void run(String vehicle) {
        System.out.println(vehicle + " 在水上运行...");
    }
}
汽车 在公路运行...
飞机 在天空运行...
轮船 在水上运行...

接口层面:

我们假设一个场景, 大家一起做家务, 张三扫地, 李四买菜. 李四买完菜回来还得做饭. 这个逻辑怎么实现呢?

错误示范:

/**
 * 做家务
 */
public interface HouseWork {
    // 扫地
    void sweepFloor();

    // 购物
    void shopping();
}

public class Zhangsan implements HouseWork{
    @Override
    public void sweepFloor() {
        // 扫地
    }

    @Override
    public void shopping() {

    }
}

public class Lisi implements HouseWork{
    @Override
    public void sweepFloor() {

    }

    @Override
    public void shopping() {
        // 购物
    }
}

首先定义了一个做家务的接口, 定义两个方法扫地和买菜. 张三扫地, 就实现扫地接口. 李四买菜, 就实现买菜接口. 然后李四买完菜回来还要做饭, 于是就要在接口类中增加一个方法cooking. 张三和李四都重写这个方法, 但只有李四有具体实现.
这样设计本身就是不合理的. 首先: 张三只扫地, 但是他需要重写买菜方法, 李四不需要扫地, 但是李四也要重写扫地方法. 第二: 这也不符合开闭原则. 增加一种类型做饭, 要修改3个类. 这样当逻辑很复杂的时候, 很容易引起意外错误.
上面这种设计不符合单一职责原则, 修改一个地方, 影响了其他不需要修改的地方.

正确示范:

/**
 * 做家务
 */
public interface Hoursework {
}

public interface Shopping extends Hoursework{
    // 购物
    void shopping();
}

public interface SweepFloor extends Hoursework{
    // 扫地
    void sweepFlooring();
}

public class Zhangsan implements SweepFloor{

    @Override
    public void sweepFlooring() {
        // 张三扫地
    }
}

public class Lisi implements Shopping{
    @Override
    public void shopping() {
        // 李四购物
    }
}

上面做家务不是定义成一个接口, 而是将扫地和做家务分开了. 张三扫地, 那么张三就实现扫地的接口. 李四购物, 李四就实现购物的接口. 后面李四要增加一个功能做饭. 那么就新增一个做饭接口, 这次只需要李四实现做饭接口就可以了.

public interface Cooking extends Hoursework{ 
    void cooking();
}

public class Lisi implements Shopping, Cooking{
    @Override
    public void shopping() {
        // 李四购物
    }

    @Override
    public void cooking() {
        // 李四做饭
    }
}

如上, 我们看到张三没有实现多余的接口, 李四也没有. 而且当新增功能的时候, 只影响了李四, 并没有影响张三. 这就是符合单一职责原则. 一个类只做一件事. 并且他的修改不会带来其他的变化.

2、开放-关闭原则 (Open-Closed Principle)

定义
开放封闭原则(OCP,Open Closed Principle)是所有面向对象原则的核心。软件设计本身所追求的目标就是封装变化、降低耦合,而开放封闭原则正是对这一目标的最直接体现。其他的设计原则,很多时候是为实现这一目标服务的.

具体描述

● 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
● 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对已有代码进行任何修改

为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。

示例

错误实例:

public class OpenClosed1 {
    public static void main(String[] args) {
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());//+新增绘制三角形
    }
}
//这是一个用于绘图的类
class GraphicEditor {
    //接收 Shape 对象,然后根据 type 来绘制不同的图形
    public void drawShape(Shape s) {
        if (s.m_type == 1)
            drawRectangle(s);
        else if (s.m_type == 2)
            drawCircle(s);
        else if (s.m_type == 3)//+新增绘制三角形
            drawTriangle(s);
    }

    //绘制矩形
    public void drawRectangle(Shape r) {
        System.out.println("绘制矩形");
    }

    //绘制圆形
    public void drawCircle(Shape r) {
        System.out.println("绘制圆形");
    }

    //+新增绘制三角形
    public void drawTriangle(Shape r) {
        System.out.println("绘制三角形");
    }
}

class Shape {
    int m_type;
}

//以前就写好的
class Rectangle extends Shape {
    Rectangle() {
        super.m_type = 1;
    }
}

//以前就写好的
class Circle extends Shape {
    Circle() {
        super.m_type = 2;
    }
}

//+新增绘制三角形
class Triangle extends Shape {
    Triangle() {
        super.m_type = 3;
    }
}
绘制矩形
绘制圆形
绘制三角形

正确示范

public class OpenClosed2 {
    public static void main(String[] args) {
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());//+新增绘制三角形
    }
}

//这是一个用于绘图的类
class GraphicEditor {
    //接收 Shape 对象,调用 draw 方法
    public void drawShape(Shape s) {
        s.draw();
    }
}

abstract class Shape {
    int m_type;

    public abstract void draw();
}

//以前就写好的
class Rectangle extends Shape {
    Rectangle() {
        super.m_type = 1;
    }

    @Override
    public void draw() {
        System.out.println("绘制矩形");
    }
}

//以前就写好的
class Circle extends Shape {
    Circle() {
        super.m_type = 2;
    }

    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
}

//+新增绘制三角形
class Triangle extends Shape {
    Triangle() {
        super.m_type = 3;
    }

    @Override
    public void draw() {
        System.out.println("绘制三角形");
    }
}
绘制矩形
绘制圆形
绘制三角形

3、里氏替换原则

定义
子类可以扩展父类的功能,但不能改变原有父类的功能。

详细描述

里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。

根据上述理解,对里氏替换原则的定义可以总结如下:
● 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
● 子类中可以增加自己特有的方法
● 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
● 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等

子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法

枪支抽象类:
public abstract class AbstractGun {
    public abstract  void shoot();
}
手枪实现类:
public class HandGun extends AbstractGun {
    public void shoot() {
       System.out.println("手机射击");     
   }
}
public class Rifle extends AbstractGun {
    public void shoot() {
       System.out.println("步枪射击");     
   }
}
士兵实现类:
public class Soldier {
  private AbstractGun gun;
  public void setGun(AbstractGun gun) {
    this.gun = gun;
  }
  public void killEnemy() {
    System.out.println("士兵杀敌人");
    gun.shoot();
  }
}

场景类:

  public class Client {
    public static void main(String[] args) {
      Soldier sanMao = new Soldier();
      sanMao.setGun(new Rifle());
      sanMao.killEnemy();
  }
}

注意
在类中调用其他类时务必要使用父类或者接口(例如Solider类的setGun(AbstractGun gun)方法),否则说明类的设计已经违背了LSP原则。
现在有个玩具枪该怎么定义?直接继承AbstractGun类吗?如下:

public class ToyGun extends AbstractGun {
  @Override
  public void shoot() {
    //玩具枪不能像真枪杀敌,不实现
  }
}

场景类:
  public class Client {
    public static void main(String[] args) {
      Soldier sanMao = new Soldier();
      sanMao.setGun(new ToyGun());
      sanMao.killEnemy();
  }
}

在这种情况下,士兵拿着玩具枪杀敌,发现业务调用类已经出现了问题,正常的业务逻辑运行结果是不正确的。(因为玩具枪并不能杀敌)ToyGun应该脱离继承,建立一个独立的类,可以与AbstractGun建立关联委托关系。类图如下:

注意
如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中发生重写或者重载,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。

2、子类中可以增加自己特有的方法
说下面两层含义之前先要明白 重载 重写(覆盖) 的区别:
重写(覆盖)的规则:
1、重写方法的参数列表必须完全与被重写的方法的相同,否则不能称其为重写而是重载.
2、重写方法的访问修饰符一定要大于被重写方法的访问修饰符(public>protected>default>private)。
3、重写的方法的返回值必须和被重写的方法的返回一致;
4、重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类;
5、被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重写。
6、静态方法不能被重写为非静态的方法(会编译出错)。
重载的规则:
1、在使用重载时只能通过相同的方法名、不同的参数形式实现。不同的参数类型可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不一样);
2、不能通过访问权限、返回类型、抛出的异常进行重载;
3、方法的异常类型和数目不会对重载造成影响;
3、当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松

public class Father {
    public Collection doSomething(HashMap  map){
        System.out.println("父类被执行了");
        return map.values();
    }
}


public class Son  extends Father{
    public Collection doSomething(Map  map){
        System.out.println("子类被执行了");
        return map.values();
    }
}


public class Client{
    public static void main(String[] args) {
        invoker();
    }

    public  static void invoker(){
           Son son = new  Son();//子类对象
           HashMap  map=new HashMap<>();
           son.doSomething(map);
    }
}

运行是”父类被执行了”,这是正确的,父类方法的参数是HashMap类型,而子类的方法参数是Map类型,子类的参数类型范围比父类大,那么子类的方法永远也不会执行。
如果我们反过来让父类的参数类型范围大于子类,并在调用时用子类去调用,我们会发现打印时的结果是”子类被执行了”,这就违反了里氏替换原则,在开发中很容易引起业务逻辑的混乱,所以类的方法重载父类的方法时,方法的前置条件(形参)要比父类方法的输入参数更宽松(相同也可以)。

4、当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等

父类的一个方法的返回值是一个类型T,子类的相同方法(重载或者重写)的返回值为S,那么里氏替换原则就要求S必须小于等于T。

4、接口隔离

原则介绍
客户端不应该依赖它不需要的接口, 即一个类对另一个类的依赖应该建立在最小的接口上。

示范

//A、B总的接口
interface InterfaceAll {
    void operation1();

    void operation2();

    void operation3();
}

//实现类A只用InterfaceAll中的operation1、operation2方法,所以实现两个方法
class A implements InterfaceAll {
    @Override
    public void operation1() {
        System.out.println("A 实现了 operation1...");
    }

    @Override
    public void operation2() {
        System.out.println("A 实现了 operation2...");
    }

    @Override
    public void operation3() {
        //A用不到,但是还需要空实现
    }
}
//实现类B只用InterfaceAll中的operation1、operation3方法,所以实现两个方法
class B implements InterfaceAll {
    @Override
    public void operation1() {
        System.out.println("B 实现了 operation1...");
    }
    @Override
    public void operation2() {
        //B用不到,但是还需要空实现
    }

    @Override
    public void operation3() {
        System.out.println("B 实现了 operation3...");
    }
}

正确示范

//接口Interface1
interface Interface1 {
    void operation1();
}

//接口Interface2
interface Interface2 {
    void operation2();
}

//接口Interface3
interface Interface3 {
    void operation3();
}

//实现类A用Interface1中的operation1和Interface2中的operation2
class A implements Interface1, Interface2 {
    @Override
    public void operation1() {
        System.out.println("A 实现了 operation1...");
    }

    @Override
    public void operation2() {
        System.out.println("A 实现了 operation2...");
    }
}

//实现类B用Interface1中的operation1和Interface3中的operation3
class B implements Interface1, Interface3 {
    @Override
    public void operation1() {
        System.out.println("B 实现了 operation1...");
    }

    @Override
    public void operation3() {
        System.out.println("B 实现了 operation3...");
    }
}

5、依赖倒置原则

定义

依赖倒置原则(dependence inversion principle): 高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

规则

  1. 每个类尽量提供接口或抽象类,或者两者都具备。
  2. 变量的声明类型尽量是接口或者是抽象类。
  3. 任何类都不应该从具体类派生。
  4. 使用继承时尽量遵循里氏替换原则。

示例

错误示例:

class Customer {
    public void shopping(ShaoguanShop shop) {
        //购物
        System.out.println(shop.sell());
    }
}

class Customer {
    public void shopping(WuyuanShop shop) {
        //购物
        System.out.println(shop.sell());
    }
}

添加功能的时候没有办法复用代码,顾客每更换一家商店,都要修改一次代码,这明显违背了开闭原则。存在以上缺点的原因是:顾客类设计时同具体的商店类绑定了,这违背了依赖倒置原则。解决方法是:定义“婺源网店”和“韶关网店”的共同接口 Shop,顾客类面向该接口编程,其代码修改如下:

class Customer {
    public void shopping(Shop shop) {
        //购物
        System.out.println(shop.sell());
    }
}



package principle;

public class DIPtest {
    public static void main(String[] args) {
        Customer wang = new Customer();
        System.out.println("顾客购买以下商品:");
        wang.shopping(new ShaoguanShop());
        wang.shopping(new WuyuanShop());
    }
}

//商店
interface Shop {
    public String sell(); //卖
}

//韶关网店
class ShaoguanShop implements Shop {
    public String sell() {
        return "韶关土特产:香菇、木耳……";
    }
}

//婺源网店
class WuyuanShop implements Shop {
    public String sell() {
        return "婺源土特产:绿茶、酒糟鱼……";
    }
}

//顾客
class Customer {
    public void shopping(Shop shop) {
        //购物
        System.out.println(shop.sell());
    }
}

6、合成复用原则

定义

合成复用原则(Composite Reuse Principle,CRP)又叫组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP)。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

继承关系实现:

组合关系实现

7、迪米特法则

定义
迪米特法则(Law of Demeter,LOD)又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话 。

实现法则

  1. 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
  2. 在类的结构设计上,尽量降低类成员的访问权限。
  3. 在类的设计上,优先考虑将一个类设置成不变类。
  4. 在对其他类的引用上,将引用其他对象的次数降到最低。
  5. 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
  6. 谨慎使用序列化(Serializable)功能。

示例

package principle;

public class LoDtest {
    public static void main(String[] args) {
        Agent agent = new Agent();
        agent.setStar(new Star("林心如"));
        agent.setFans(new Fans("粉丝我"));
        agent.setCompany(new Company("中国传媒有限公司"));
        agent.meeting();
        agent.business();
    }
}

//经纪人
class Agent {
    private Star myStar;
    private Fans myFans;
    private Company myCompany;

    public void setStar(Star myStar) {
        this.myStar = myStar;
    }

    public void setFans(Fans myFans) {
        this.myFans = myFans;
    }

    public void setCompany(Company myCompany) {
        this.myCompany = myCompany;
    }

    public void meeting() {
        System.out.println(myFans.getName() + "与明星" + myStar.getName() + "见面了。");
    }

    public void business() {
        System.out.println(myCompany.getName() + "与明星" + myStar.getName() + "洽淡业务。");
    }
}

//明星
class Star {
    private String name;

    Star(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

//粉丝
class Fans {
    private String name;

    Fans(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

//媒体公司
class Company {
    private String name;

    Company(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Circ.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值