一、单一职责
- 类(或方法)功能的专一性。一个类(或方法)不应该承担太多功能,一个类(或方法)最好只承担 一种类型 的功能。
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(); } }