【设计模式】1、设计模式七大原则

简介: 【设计模式】1、设计模式七大原则


一、单一职责

  • 类(或方法)功能的专一性。一个类(或方法)不应该承担太多功能,一个类(或方法)最好只承担 一种类型 的功能。
public class SingleResponsibility {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.run("汽车");
        vehicle.run("火车");
        vehicle.run("自行车");
        vehicle.run("飞机"); // 有问题
        vehicle.run("轮船"); // 有问题
    }
}
class Vehicle {
    public void run(String vehicleName) {
        System.out.println(vehicleName + "在公路上行驶");
    }
}

Vehicle 类既处理陆地上的交通工具,又处理天空中的交通工具;它的作用太广泛了,不单一。


public class SingleResponsibility {
    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
        roadVehicle.run("汽车");
        roadVehicle.run("火车");
        roadVehicle.run("自行车");
        SkyVehicle skyVehicle = new SkyVehicle();
        skyVehicle.run("飞机");
        WaterVehicle waterVehicle = new WaterVehicle();
        waterVehicle.run("轮船");
    }
}
class RoadVehicle {
    public void run(String vehicleName) {
        System.out.println(vehicleName + "在公路上行驶");
    }
}
class WaterVehicle {
    public void run(String vehicleName) {
        System.out.println(vehicleName + "在水中行驶");
    }
}
class SkyVehicle {
    public void run(String vehicleName) {
        System.out.println(vehicleName + "在天空中行驶");
    }
}

在类级别遵循了单一职责原则

当业务功能比较简单的时候也没有必要将其拆分为多个类(如下所示)


public class SingleResponsibility {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.run("汽车", VehicleType.ROAD);
        vehicle.run("火车", VehicleType.ROAD);
        vehicle.run("自行车", VehicleType.ROAD);
        vehicle.run("飞机", VehicleType.SKY);
        vehicle.run("轮船", VehicleType.WATER);
    }
}
enum VehicleType {
    ROAD, WATER, SKY;
}
class Vehicle {
    public void run(String name, VehicleType type) {
        String sentence = "";
        if (type == VehicleType.ROAD) {
            sentence = "在公路上行驶";
        } else if (type == VehicleType.WATER) {
            sentence = "在水中行驶";
        } else if (type == VehicleType.SKY) {
            sentence = "在天空中行驶";
        }
        System.out.println(name + sentence);
    }
    // public void roadRun(String name) {}
    // public void skyRun(String name) {}
    // public void waterRun(String name) {}
}

Vehicle 类不符合单一职责原则,但其功能简单 。

当功能非常非常简单的时候,不一定必须遵循单一职责原则。(杀鸡别用宰牛刀)

  • 降低了类的功能的复杂度
  • 提高了代码的维护性
  • 代码修改导致连带错误几率降低


二、接口隔离

① Clients should not be forced to depend on methods they do not use. 客户端不应该被迫依赖它不使用的方法。

② The dependency of one class to another one should depend on the smallest possible interface. 一个类对另一个类的依赖应该建立在最小的接口上。

public class InterfaceSegregation {
    public static void main(String[] args) {
        InOutAbleImpl1 impl1 = new InOutAbleImpl1();
        InOutAbleImpl2 impl2 = new InOutAbleImpl2();
        
        Cat cat = new Cat();
        cat.use1(impl1);
        cat.use2(impl1);
        cat.use3(impl1);
        Dog dog = new Dog();
        dog.use1(impl2);
        dog.use2(impl2);
        dog.use3(impl2);
    }
}
class Cat {
    public void use1(InOutAble inOutAble) {
        inOutAble.openDoor();
    }
    public void use2(InOutAble inOutAble) {
        inOutAble.pushGoods();
    }
    public void use3(InOutAble inOutAble) {
        inOutAble.writeGoodsName();
    }
}
class Dog {
    public void use1(InOutAble inOutAble) {
        inOutAble.openDoor();
    }
    public void use2(InOutAble inOutAble) {
        inOutAble.popGoods();
    }
    public void use3(InOutAble inOutAble) {
        inOutAble.writePeopleName();
    }
}
/**
 * 存取东西的接口
 */
interface InOutAble {
    // 类型1
    void openDoor(); // 打开门
    // 类型2
    void pushGoods(); // 放入货物
    void writeGoodsName(); // 登记货物名字
    // 类型3
    void popGoods(); // 取出货物
    void writePeopleName(); // 登记取货人的名字
}
class InOutAbleImpl1 implements InOutAble {
    @Override
    public void openDoor() {
        System.out.println("InOutAbleImpl1 - openDoor");
    }
    @Override
    public void pushGoods() {
        System.out.println("InOutAbleImpl1 - pushGoods");
    }
    @Override
    public void writeGoodsName() {
        System.out.println("InOutAbleImpl1 - writeGoodsName");
    }
    @Override
    public void popGoods() {
        System.out.println("InOutAbleImpl1 - popGoods");
    }
    @Override
    public void writePeopleName() {
        System.out.println("InOutAbleImpl1 - writePeopleName");
    }
}
class InOutAbleImpl2 implements InOutAble {
    @Override
    public void openDoor() {
        System.out.println("InOutAbleImpl2 - openDoor");
    }
    @Override
    public void pushGoods() {
        System.out.println("InOutAbleImpl2 - pushGoods");
    }
    @Override
    public void writeGoodsName() {
        System.out.println("InOutAbleImpl2 - writeGoodsName");
    }
    @Override
    public void popGoods() {
        System.out.println("InOutAbleImpl2 - popGoods");
    }
    @Override
    public void writePeopleName() {
        System.out.println("InOutAbleImpl2 - writePeopleName");
    }
}


public class InterfaceSegregation {
    public static void main(String[] args) {
        OpenPushImpl impl1 = new OpenPushImpl();
        OpenPopImpl impl2 = new OpenPopImpl();
        Cat cat = new Cat();
        cat.use1(impl1);
        cat.use2(impl1);
        cat.use3(impl1);
        Dog dog = new Dog();
        dog.use1(impl2);
        dog.use2(impl2);
        dog.use3(impl2);
    }
}
class Cat {
    public void use1(OpenPushImpl openPushImpl) {
        openPushImpl.openDoor();
    }
    public void use2(OpenPushImpl openPushImpl) {
        openPushImpl.pushGoods();
    }
    public void use3(OpenPushImpl openPushImpl) {
        openPushImpl.writeGoodsName();
    }
}
class Dog {
    public void use1(OpenPopImpl openPopImpl) {
        openPopImpl.openDoor();
    }
    public void use2(OpenPopImpl openPopImpl) {
        openPopImpl.popGoods();
    }
    public void use3(OpenPopImpl openPopImpl) {
        openPopImpl.writePeopleName();
    }
}
interface OpenDoorAble {
    void openDoor(); // 打开门
}
interface PushAble {
    void pushGoods(); // 放入货物
    void writeGoodsName(); // 登记货物名字
}
interface PopAble {
    void popGoods(); // 取出货物
    void writePeopleName(); // 登记取货人的名字
}
class OpenPushImpl implements OpenDoorAble, PushAble {
    @Override
    public void openDoor() {
        System.out.println("OpenPushImpl - openDoor");
    }
    @Override
    public void pushGoods() {
        System.out.println("OpenPushImpl - pushGoods");
    }
    @Override
    public void writeGoodsName() {
        System.out.println("OpenPushImpl - writeGoodsName");
    }
}
class OpenPopImpl implements OpenDoorAble, PopAble {
    @Override
    public void openDoor() {
        System.out.println("OpenPopImpl - openDoor");
    }
    @Override
    public void popGoods() {
        System.out.println("OpenPopImpl - popGoods");
    }
    @Override
    public void writePeopleName() {
        System.out.println("OpenPopImpl - writePeopleName");
    }
}


三、依赖倒置(倒转)

🍬 ① 高层模块不应该依赖低层模块(类),二者都应该依赖于抽象(接口)

🍬 ② 抽象(接口)不应该依赖细节(实现类),而是细节依赖于抽象

🍬 ③ 依赖倒置的中心思想是:面向接口(抽象)编程

🍬 ④ 依赖倒置设计理念:相对于细节的多变性,抽象的东西要稳定得多。以抽象为基础搭建的架构比以细节为基础搭建的架构要稳定

🍬 ⑤ 使用接口或抽象类的作用是:制定规范(协议);把展现细节的任务交给接口的实现类

接口 ➡️ 抽象

实现类 ➡️ 细节

public class DependencyInversion {
    public static void main(String[] args) {
        Person person = new Person();
        person.sendMessage(new QQMessage("用QQ问候一下小明"));
        person.sendMessage(new WechatMessage("用微信问候一下小明"));
    }
}
class Person {
    public void sendMessage(QQMessage qqMessage) {
        System.out.println(qqMessage.buildMessage());
    }
    public void sendMessage(WechatMessage wechatMessage) {
        System.out.println(wechatMessage.buildMessage());
    } 
}
class QQMessage {
    private String message;
    public QQMessage(String message) {
        this.message = message;
    }
    public String buildMessage() {
        return "QQ Message: " + message;
    }
}
class WechatMessage {
    private String message;
    public WechatMessage(String message) {
        this.message = message;
    }
    public String buildMessage() {
        return "Wechat Message: " + message;
    }
}

😰 假如版本升级,还想发送 抖音消息 的话:① 需要创建一个 TiktokMessage 类;② 需要在 Person 类中再重载一个 sendMessage(TiktokMessage tiktokMessage) 方法

😰 每次版本升级,增加新的发送消息的方式的时候对代码的改动非常大。假如不止一个用户(不仅仅只有 Person 类),哪改动就更加巨大了


面向接口编程:

public class DependencyInversion {
    public static void main(String[] args) {
        Person person = new Person();
        person.sendMessage(new QQMessage("用QQ问候一下小明"));
        person.sendMessage(new WechatMessage("用微信问候一下小明"));
    }
}
class Person {
    public void sendMessage(IMessage iMessage) {
        System.out.println(iMessage.buildMessage());
    }
}
interface IMessage {
    String buildMessage();
}
/**
 * 发送 QQ 消息
 */
class QQMessage implements IMessage {
    private String message;
    public QQMessage(String message) {
        this.message = message;
    }
    @Override
    public String buildMessage() {
        return "QQ Message: " + message;
    }
}
/**
 * 发送微信消息
 */
class WechatMessage implements IMessage {
    private String message;
    public WechatMessage(String message) {
        this.message = message;
    }
    public String buildMessage() {
        return "Wechat Message: " + message;
    }
}


四、里氏替换

继承优点: 🍬 提高代码的复用性(子类继承父类后可使用父类的非 private 关键字修饰的的成员)

public class Animal {
    public void eat() {
        System.out.println("Animal - public void eat()");
    }
    protected void drink() {
        System.out.println("Animal - protected void drink()");
    }
    void play() {
        System.out.println("Animal - void play()");
    }
}
class Dragon extends Animal {
    public void use() {
        eat(); // Animal - public void eat()
        drink(); // Animal - protected void drink()
        play(); // Animal - void play()
    }
    public static void main(String[] args) {
        Dragon dragon = new Dragon();
        dragon.use();
    }
}

继承缺点: 🍬 ① 继承关系过去庞大的话,整个代码结构会很乱

🍬 ② 代码耦合性变高

🍬 ③ 代码稳定性变差(子类可以重写父类的方法。重写之后,运行时究竟调用的是父类的方法还是子类重写的方法很难判断)

🍬 ④ 如果有多处直接使用父类方法的实现,但凡父类方法修改了,所有依赖该父类方法的地方都得考虑兼容性(考虑代码是否会产生 bug)


(1) 子类可以实现父类的抽象方法,【不要覆盖父类的非抽象方法】 (2) 子类可以可以增加自己特有的实现,不要影响父类的非抽象方法【你用父类的可以,但不要改】 (3) 子类方法重载父类方法的时候,方法的形参要比父类方法的形参更宽松(父类方法的形参得是子类方法的形参的父类型)

public class Main {
    public static void main(String[] args) {
        Parent parent = new Parent();
        parent.m2(new ArrayList<>());
        Son son = new Son();
        son.m2(new ArrayList<>());
        
        /*
            Parent - m2(ArrayList<String>)
            Parent - m2(ArrayList<String>)
         */
    }
}
class Parent {
    public void m1() {
        System.out.println("Parent - m1()");
    }
    public void m2(ArrayList<String> list) {
        System.out.println("Parent - m2(ArrayList<String>)");
    }
}
class Son extends Parent {
    // 不符合里氏替换原则(子类不应该重写父类的非抽象方法)
    @Override
    public void m1() {
        System.out.println("Son - m1()");
    }
    // 子类重载父类的方法(子类重载的方法的参数类型要比父类被重载的方法的参数类型大)
    // 这样才符合里氏替换原则, 子类增加代码(如新增一个方法)不会影响父类方法的使用
    public void m2(List<String> list) {
        System.out.println("Son - m2(List<String>)");
    }
}

(4) 子类实现父类的抽象方法的时候,方法的返回值应比父类的更加严格

五、迪米特法则(Law of Demeter)

🍬 只与你的直接朋友交谈,不与陌生人交谈【降低类与类之间的耦合】

A ➡️ B ➡️ C

① A 和 B 是直接朋友

② B 和 C 是直接朋友

③ A 和 C 是陌生人

④ 若 A 想使用 C 中的方法,需要通过 B

🍬 直接朋友:

① 当前对象本身

② 当前对象的成员变量

③ 当前对象的成员方法的返回类型

④ 当前对象的成员方法的参数

class Parent {
    public void m() {
        // 在方法内部 new 出来的是非直接朋友
        User user = new User();
    }
} 
class User {
}

六、开闭

🎄 Open Close Principe:软件对象(类、模块、方法等)应该对扩展开放,对修改关闭

🎄 用抽象构建框架,用实现扩展细节

🎄 开放 服务方 的拓展,关闭 消费方 的修改


public class OpenClosePrincipe {
    public static void main(String[] args) {
        // 对消费方的修改关闭
        // 尽量少修改原先的代码
        MilkTeaFactory factory = new MilkTeaFactory();
        factory.makeMilkTea(MilkTeaType.APPLE);
        factory.makeMilkTea(MilkTeaType.BANANA);
    }
}
interface MilkTeaAble {
}
class AppleMilkTea implements MilkTeaAble {
    public AppleMilkTea() {
        System.out.println("苹果奶茶");
    }
}
class BananaMilkTea implements MilkTeaAble {
    public BananaMilkTea() {
        System.out.println("香蕉奶茶");
    }
}
enum MilkTeaType {
    APPLE, BANANA
}
class MilkTeaFactory {
    public MilkTeaAble makeMilkTea(MilkTeaType type) {
        switch (type) {
            case APPLE:
                return new AppleMilkTea();
            case BANANA:
                return new BananaMilkTea();
        }
        return null;
    }
}

public class OpenClosePrincipe {
    // 修改依赖的类型(消费方代码没有修改)
    private static MilkTeaAble factory = new WatermelonMilkTeaFactory();
    public static void main(String[] args) {
        factory.milkTea();
    }
}
interface MilkTeaAble {
    void milkTea();
}
class AppleMilkTeaFactory implements MilkTeaAble {
    @Override
    public void milkTea() {
        System.out.println("苹果奶茶");
    }
}
class BananaMilkTeaFactory implements MilkTeaAble {
    @Override
    public void milkTea() {
        System.out.println("香蕉奶茶");
    }
}
class WatermelonMilkTeaFactory implements MilkTeaAble {
    @Override
    public void milkTea() {
        System.out.println("西瓜奶茶");
    }
}


七、合成复用

📖 通过对象 组合、聚合、依赖 达成代码复用,而不是继承

class Animal {
    public void eat() {
        System.out.println("Animal - eat()");
    }
    public void drink() {
        System.out.println("Animal - drink()");
    }
    public void play() {
        System.out.println("Animal - play()");
    }
}
/**
 * 继承(不推荐, 不符合合成复用原则)
 */
class People1 extends Animal {
    public void use() {
        eat();
        drink();
        play();
    }
}
/**
 * 依赖(推荐)
 */
class People2 {
    public void use(Animal animal) {
        animal.eat();
        animal.drink();
        animal.play();
    }
}
/**
 * 聚合(推荐)
 */
class People3 {
    private Animal animal;
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
    public void use() {
        animal.eat();
        animal.drink();
        animal.play();
    }
}
/**
 * 组合(推荐)
 */
class People4 {
    private Animal animal = new Animal();
    public void use() {
        animal.eat();
        animal.drink();
        animal.play();
    }
}
相关文章
|
7月前
|
设计模式 前端开发 Java
设计模式之六大基本原则
设计模式之六大基本原则
54 0
|
4月前
|
设计模式
设计模式七大原则
这篇文章介绍了设计模式中的七大原则,特别强调了单一职责原则,即一个类应该只有一个引起其行为变化的原因,以确保类功能的高内聚和低耦合。
|
4月前
|
设计模式 存储 前端开发
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
|
6月前
|
设计模式 供应链
设计模式六大原则之迪米特法则
设计模式六大原则之迪米特法则
|
6月前
|
设计模式
设计模式六大原则之依赖倒置原则
设计模式六大原则之依赖倒置原则
|
3月前
|
设计模式 Java 关系型数据库
设计模式——设计模式简介和七大原则
设计模式的目的和核心原则、单一职责原则、接口隔离原则、依赖倒转原则、里氏替换原则、开闭原则、迪米特法则、合成复用原则
设计模式——设计模式简介和七大原则
|
4月前
|
设计模式 算法 开发者
设计模式问题之最小知识原则(迪米特法则)对代码设计有何影响,如何解决
设计模式问题之最小知识原则(迪米特法则)对代码设计有何影响,如何解决
|
4月前
|
设计模式 前端开发 JavaScript
React开发设计模式及原则概念问题之什么是HOC(Higher-order component),HOC遵循的设计原则都有哪些
React开发设计模式及原则概念问题之什么是HOC(Higher-order component),HOC遵循的设计原则都有哪些
|
4月前
|
设计模式 前端开发 JavaScript
React开发设计模式及原则概念问题之什么是设计模式,单一职责原则如何理解
React开发设计模式及原则概念问题之什么是设计模式,单一职责原则如何理解
|
6月前
|
设计模式 uml
设计模式学习心得之前置知识 UML图看法与六大原则(下)
设计模式学习心得之前置知识 UML图看法与六大原则(下)
46 2