面向对象编程
设计原则与设计模式
目标:提高面向对象设计复用性的设计原则。满足可扩展性,灵活性和可插入性。
复用:抽象模块要尽量独立于具体层次的设计。
面向对象的设计原则
- 开闭原则OCP: Open-Closed Principle
- 里氏代换原则LSP: Liskov Substitution Principle
- 依赖倒置原则DIP: Dependency Inversion Principle
- 接口隔离原则ISP: Interface Segregation Principle
- 组合复用原则CRP: Composition Reuse Principle
- 迪米特法则LoD: Law of Demeter
- 单一职责原则SRP
开闭原则OCP: Open-Closed Principle
-
定义:软件组成实体应该是对扩展开放的(可扩展的),但是对修改是关闭的。试图设计永远不需要改变的模块(关键在于抽象层次结构的设计)
-
目标:尝试在软件系统中减少不满足OCP原则的模块数量。同时做到解耦!
示例代码1:
class Part { protected double basePrice; public void setPrice(double price) { basePrice = price; } public double getPrice() { return basePrice; } } class Memory extends Part { } class Disk extends Part { }
public class TestOCP { public static void main(String[] args) { Part p1 = new Memory(); p1.setPrice(599); Part p2 = new Disk(); p2.setPrice(499); Part[] com = {p1, p2}; System.out.println(totalprice(com)); } public static double totalprice(Part[] parts) { double total = 0.0; for (int i = 0; i < parts.length; i++) { total += parts[i].getPrice(); } return total; } } // 当考虑到内存折扣时,上述代码需要被修改,让其满足可复用原则
public static double totalprice(Part[] parts) { double total = 0.0; for (int i = 0; i < parts.length; i++) { if (parts[i] instanceof Memory) total += parts[i].getPrice() * 0.9; // 这样做效率比较低 建议不要这样复用 else total += parts[i].getPrice(); } return total; } // 每次发生变化时,都需要修改这个方法
// 使用这种方法,我们可以在运行时动态地设置Part对象所引用的PricePoilcy对象 // 尝试Memory类继承自Part类 减少对于上述totalprice方法的修改,但是每次计价原则发生变化时需要修改实体类,存在缺陷 class Memory extends Part { public double getPrice() { return basePrice * 0.9; } }
- 增加方法类:采用一个PricePolicy类,通过对其进行继承以提供不同的计价策略
// 采用一个PricePolicy类,通过对其进行继承以提供不同的计价策略 class Part { protected double basePrice; private PricePolicy pricePolicy; public void setPricePolicy(PricePolicy policy) { pricePolicy = policy; } public void setPrice(double price) { basePrice = price; } public double getPrice() { if (pricePolicy == null) return basePrice; else return pricePolicy.getPrice(basePrice); } } class PricePolicy { public double getPrice(double basePrice) { return basePrice; } } // 当需要进行促销策略的时候,直接在具体的实现类修改即可 class Sale extends PricePolicy { private double discount; public void setDiscount(double discount) { this.discount = discount; } public double getPrice(double basePrice) { return basePrice * discount; } } // 使用促销策略修改之后的具体实现类 public class TestOCP { public static void main(String[] args) { // TODO Auto-generated method stub Part p1 = new Memory(); p1.setPrice(599); Part p2 = new Disk(); p2.setPrice(499); Sale sale = new Sale(); sale.setDiscount(0.75); p2.setPricePolicy(sale); Part[] com = {p1, p2}; System.out.println(totalprice(com)); } } //使用这种方法,我们可以在运行时动态地设置Part对象所引用的PricePoilcy对象
示例2:绘制GUI图形
// 设计思想,每增加一种新的图形,都要尽量满足OCP原则 class Shape { } class Circle extends Shape { void drawCircle() { System.out.println("I am drawing a circle in switch!"); } } class Square extends Shape { void drawSquare() { System.out.println("I am drawing a square in switch!"); } } import java.util.ArrayList; import java.util.List; public class SwitchDraw { public static void main(String[] args) { List<Shape> shapeList=new ArrayList<Shape>(); Circle c=new Circle(); Square s=new Square(); shapeList.add(c); shapeList.add(s); switchDraw(shapeList); } static void switchDraw(List shapeList) { for (int i=0;i<shapeList.size();i++)// 通过for循环取出list中每//个shape { if(shapeList.get(i) instanceof Circle) { Circle circle= (Circle)shapeList.get(i); circle.drawCircle(); }else if(shapeList.get(i) instanceof Square) { Square square= (Square)shapeList.get(i); square.drawSquare(); } } } } // 通过这样的实现方式会导致整个switch/if-else循环分支量过大,不推荐
- 我们寻求遵循OCP原则的设计方式
- 步骤:
- 编写一个shape抽象类,这个类仅有一个抽象方法draw(),所有形状都从这个类派生
- 当绘制一种新的形状,只需要增加一个新的shape类的派生类。而DrawAllShapes 函数并不需要改变。
import java.util.ArrayList; import java.util.List; public class TestOCP2 { public static void main(String[] args) { List<Shape> shapeList = new ArrayList<Shape>(); Circle c = new Circle(); Square s = new Square(); shapeList.add(c); shapeList.add(s); DrawAllShape drawAllShape = new DrawAllShape(); drawAllShape.drawAllShapes(shapeList); } } class DrawAllShape { void drawAllShapes(List<Shape> shapeList) { for (int i = 0; i < shapeList.size(); i++) // 通过for循环取出list中每个//shape { Shape shape = (Shape) shapeList.get(i); // 这里注意有一个强制转变为父类对象的过程,然后我们使用了多态 shape.draw(); } } }
- 设计模式基于设计思想,目标是达到代码复用,可维护性好。
- 模式描述了一类问题的通用 的解决方案的模板。
依赖倒置原则DIP: Dependency Inversion Principle
- 定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象
- 名词辨析:
- 抽象是指抽象类或接口,两者是不能够实例化的
- 细节是指具体的实现类
- 本质:通过抽象使得各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。将具体类之间的依赖转变为抽象类或接口之间的依赖
- 难以实现,是开闭原则的基础。
- 抽象工厂模式最能体现DIP原则,关注工厂的产品和接口而不关注具体细节。
- 核心:针对接口编程,不要针对实现编程
- 举个栗子:
-
强调一点:变量不要持有具体类的引用,而与抽象类无关;多态!
-
依赖倒置的三种实现方式:
- 通过构造函数传递依赖对象(构造函数注入)
public interface IDriver { //是司机就应该会驾驶汽车 public void drive(); } public class Driver implements IDriver { private ICar car; //构造函数注入 public Driver(ICar _car) { this.car = _car; } //司机的主要职责就是驾驶汽车 public void drive() { this.car.run(); } }
- 通过setter方法传递依赖对象
public interface IDriver { //车辆型号 public void setCar(ICar car); //是司机就应该会驾驶汽车 public void drive(); } public class Driver implements IDriver { private ICar car; public void setCar(ICar car) { this.car = car; } //司机的主要职责就是驾驶汽车 public void drive() { this.car.run(); } } public interface ICar { //是汽车就应该能跑 public void run(); } public class Benz implements ICar { public void run() { System.out.println("奔驰汽车开始运行..."); } } public class Client { public static void main(String[] args) { IDriver zhangSan = new Driver(); ICar benz = new Benz(); //张三开奔驰车 zhangsan.setCar(benz); } }
- 接口声明实现依赖对象
public interface ICar { //是汽车就应该能跑 public void run(); } public class Benz implements ICar { //汽车肯定会跑 public void run() { System.out.println("奔驰汽车开始运行..."); } } public interface IDriver { //是司机就应该会驾驶汽车 public void drive(ICar car); } public class Driver implements IDriver { //司机的主要职责就是驾驶汽车 public void drive(ICar car) { car.run(); } } public class Client { public static void main(String[] args) { IDriver zhangSan = new Driver(); ICar benz = new Benz(); //张三开奔驰车 zhangSan.drive(benz); } } // 优点:司机和汽车之间做到了松耦合,新汽车加入时扩展汽车类, // 扩展汽车类时司机不需要修改(稳定性)
-
这里有个朴素的例子,学习一下
// demo
public interface IManeuverable {
public void left();
public void right();
public void forward();
public void reverse();
public void climb();
public void dive();
public void setSpeed(double speed);
public double getSpeed();
}
public class Car implements IManeuverable {
// Code here.
}
public class Boat implements IManeuverable {
// Code here.
}
public void travel(IManeuverable vehicle) {
vehicle.setSpeed(35.0);
vehicle.forward();
vehicle.left();
vehicle.climb();
}
设计模式的基本要素
- 模式名称,问题,解决方案,效果
设计模式的分类
- 创建型:抽象了对象实例化的过程
- 结构型:如何组合类和对象以获得更大的结构
- 行为型:描述算法和对象间职责的分配
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JoQ1WEAp-1655602874775)(https://cdn.jsdelivr.net/gh/cccccwwl/images@main/img/202206171953368.png)]
设计模式-工厂模式
- 分类:
- 简单工厂模式(Simple Factory):不利于产生系列产品
- 工厂方法模式(Factory Method):又称为多形性工厂
- 抽象工厂模式(Abstract Factory):又称为工具箱,产生产品族,但不利于产生新的产品
- 三种模式自上而下逐步抽象,并且更加具有一般性
简单工厂
-
解决方案
// 代码实现demo
// 客户端不但知道了接口,同时还知道了具体的实现就是ImplA
// 这里使用了接口的“封装隔离”思想,但是用户只是知道接口是Api,不知道ImplA的具体实现细节
/** 接口的定义,该接口可以通过简单工厂来创建*/
public interface Api {
/** * 示意,具体的功能方法的定义 */
public void operation(String s);
}
/*** 接口的具体实现对象A . */
public class ImplA implements Api{
public void operation(String s) {
//实现功能的代码,示意一下
System.out.println("ImplA s=="+s);
}
}
// 模块内部是可以知道实现类Factory
/**
* 工厂类,用来创造Api对象
*/
public class Factory {
/**
* 具体的创造Api对象的方法
*
* @param condition 示意,从外部传入的选择条件 ; @return 创造好的Api对象
*/
public static Api createApi(int condition) {
//根据某些条件去选择究竟创建哪一个具体的实现对象, 这些条件可以从外部传入,也可以从其它途径获取。如果只有一个实现,可省略条件。
Api api = null;
if (condition == 1) {
api = new ImplA();
} else if (condition == 2) {
api = new ImplB();
}
return api;
}
}
// 客户端通过Factory类的调用实现对于接口方法的调用,实现相应的功能,不关注具体类的实现
/**
* 客户端,使用Api接口
*/
public class Client {
public static void main(String[] args) {
//通过简单工厂来获取接口对象
Api api = Factory.createApi(1);
api.operation("正在使用简单工厂");
}
}
// 优点:客户端不需要修改代码
// 缺点:当需要增加新的实现类的时候,
// 不仅需新加实现类,还要修改工厂类,违反了开闭原则。
- 关注点:“选择合适的实现类”来创建实例对象,选择的条件来自于客户端。实现了客户端与具体实现类之间的解耦操作!选择实现
- 客户端需要知道每个调用参数的含义(condition),暴露一定的内部实现细节。
- 面向接口编程的开端
工厂方法
-
思想:不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。
-
理解:针对不同的产品来创建不同的工厂,一个产品对应于一个工厂。工厂方法模式和简单工厂一样定义了一个创建对象的接口,但由子类来决定实例化的类是哪一个。
-
解决方案:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vE7PGpYo-1655602874776)(https://cdn.jsdelivr.net/gh/cccccwwl/images@main/img/202206172018836.png)]
-
Product: 定义工厂方法所创建的对象的接口,也就是实际需要使用的对象的接口。
-
ConcreteProduct:具体的Product接口的实现对象。
-
Facotry:创建器,声明工厂方法,工厂方法通常会返回一个Product类型的实例对象,而且多是抽象方法。也可以在Factory里面提供工厂方法的默认实现,让工厂方法返回一个缺省的Product类型的实例对象。
-
ConcreteFacotry:具体的创建器对象,(覆盖)实现AbstractFacotry定义的工厂方法,返回具体的Product实例。
// 实现代码demo
/* 工厂方法所创建的对象的接口*/
abstract class Product {
//可以定义Product的属性和方法
}
/** * 具体的Product对象 */
public class ConcreteProduct extends Product {
//实现Product要求的方法
}
/*** 客户端使用Facotry对象的情况下,Factory的基本实现结构*/
abstract class Factory {
public abstract Product factoryMethod();
}
/**
* 具体的创建器实现对象
*/
class ConcreteFactory extends Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
/*客户端程序*/
public class Client {
public static void main(String[] args) {
//通过简单工厂来获取接口对象
Factory factory;
factory = new ConcreteFactory();
//可通过配置文件实现
Product product;
product = factory.factoryMethod();
}
}
// 具体的工厂类在实现工厂方法的时候既可以创建产品类对象,又可以负责产品对象的初始化以及其他的配置性工作。
- 对象调用顺序示意图
- 示例:工厂方法模式设计日记记录器
- 要求:Logger接口充当抽象产品,其子类FileLogger和DatabaseLogger充当具体产品,LoggerFactory接口充当抽象工厂,其子类FileLoggerFactory和DatabaseLoggerFactory充当具体工厂
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-olPdBBYp-1655602874777)(https://cdn.jsdelivr.net/gh/cccccwwl/images@main/img/202206172032912.png)]
// 代码实现demo
//日志记录器接口:
//抽象产品
interface Logger {
public void writeLog();
}
//数据库日志记录器:具体产品
class DatabaseLogger implements Logger {
public void writeLog() {
System.out.println("数据库日志记录");
}
}
//文件日志记录器:具体产品
class FileLogger implements Logger {
public void writeLog() {
System.out.println("文件日志记录。");
}
}
//日志记录器工厂接口:抽象工厂
interface LoggerFactory {
public Logger createLogger();
}
//数据库日志记录器工厂类:具体工厂
class DatabaseLoggerFactory implements LoggerFactory {
public Logger createLogger() {
//连接数据库,代码省略 ;创建数据库日志记录器对象
Logger logger = new DatabaseLogger();
//初始化数据库日志记录器代码省略
return logger;
}
}
//文件日志记录器工厂类:具体工厂
class FileLoggerFactory implements LoggerFactory {
public Logger createLogger() {
//创建文件日志记录器对象
Logger logger = new FileLogger();
//创建文件,代码省略
return logger;
}
}
// 客户端
class Client {
public static void main(String args[]) {
LoggerFactory factory;
//可引入配置文件实现
factory = new FileLoggerFactory();
Logger logger;
logger = factory.createLogger();
logger.writeLog();
}
}
-
评价工厂方法:使用多态来应对增加新产品的问题,使得一个类的实例化延迟到具体的实现类
- 优点:实现了责任分割,使得客户端代码“针对接口编程”,保持对变化的“关闭”
- 缺点:没有完全做到“开-闭”,对工厂方法内的修改关闭,增加新的产品需要修改工厂方法的代码
-
示例学习:工厂方法应用到“快餐店”问题
抽象工厂
- 思想:多个工厂可以创建一系列的对象,与简单工厂和工厂方法创建的单个产品对象不同。
- 定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
- 将工厂方法和抽象工厂放在一起对比:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13HHh7Nl-1655602874778)(https://cdn.jsdelivr.net/gh/cccccwwl/images@main/img/202206172321934.png)]
// 实现代码demo
/**
* 抽象工厂的接口,声明创建抽象产品对象的操作
*/
public interface Factory {
/**
* 示例方法,创建抽象产品A的对象
*
* @return 抽象产品A的对象
*/
public ProductA createProductA();
/**
* 示例方法,创建抽象产品B的对象
*
* @return 抽象产品B的对象
*/
public ProductB createProductB();
}
/**
* 抽象产品A的接口
*/
public interface ProductA {
//定义抽象产品A相关的操作
}
/**
* / * 产品A的具体实现
*/
public class ProductA1 implements ProductA {
//实现产品A的接口中定义的操作
}
public class ProductA2 implements ProductA {
//实现产品A的接口中定义的操作
}
//ProductB系列是类似的代码
/**
* 具体的工厂实现对象,实现创建具体的产品对象的操作
*/
public class Factory1 implements Factory {
public ProductA createProductA() {
return new ProductA1();
}
public ProductB createProductB() {
return new ProductB1();
}
}
/**
* 具体的工厂实现对象,实现创建具体的产品对象的操作
*/
public class Factory2 implements Factory {
public ProductA createProductA() {
return new ProductA2();
}
public ProductB createProductB() {
return new ProductB2();
}
}
public class Client {
public static void main(String[] args) {
//创建抽象工厂对象
Factory af = new Factory1();
//通过抽象工厂来获取一系列的对象,如产品A和产品B
af.createProductA();
af.createProductB();
}
}
- 本质:选择产品簇的实现;简单工厂和工厂方法侧重于对单个产品的实现和处理。
- 对于抽象工厂而言,如果系统只知道产品接口而不关心具体实现的时候,在多个产品系列中选一个配置的时候,以及动态切换产品簇的时候建议使用抽象工厂模式
- 联系:抽象工厂退化为工厂方法 :当产品簇就一个产品;工厂方法退化为简单工厂 当一个工厂生产所有产品时。
设计模式-Singleton单件/单例模式
- 定义:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例模式,多用于注册表,日志,线程池,缓存等只需要一个实例的对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ItKcdZ8X-1655602874778)(https://cdn.jsdelivr.net/gh/cccccwwl/images@main/img/202206180004075.png)]
- 单例模式的实现
// 经典版本
public class Singleton {
// 注意采用静态变量和静态方法,加载整个类时只执行一次
private static Singleton uniqueInstance;
// other useful instance variables here
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// other useful methods here
}
// 多线程版本
public class Singleton {
private static Singleton uniqueInstance;
// other useful instance variables here
private Singleton() {
}
// 这里添加了synchronized关键字,余下的和demo一样
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// other useful methods here
}
// 急切式创建单例模式
public class Singleton {
// 直接为该单例类创建一个实例对象
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
// 直接返回唯一的单例对象
public static Singleton getInstance() {
return uniqueInstance;
}
}
// 了解:双重加锁 volatile 关键字
设计模式-Bridge(桥梁)模式
- 提出的原因:继承会使得问题越来越复杂,不必要的继承导致类爆炸,对于每一种可能的情况都实现一个具体类,这是不可取的
- 设计原则:组合优先原则,在应对动态变化时优先选择组合
- 目标:抽象部分与它的实现部分分离,两者独立变化
- 继承的优缺点:优点:可以很容易修改或者扩展父类的实现;缺点:同时破坏封装性(白盒复用),父类发生改变,子类受到影响,继承为静态操作,不能在运行时发生改变。
- 组合复用的优缺点:优点:不破坏封装(黑盒复用),只依赖接口(依赖少),动态的(灵活性高,可以将成员对象动态替换为另一个类型相同的对象);缺点:对象数量多,系统在使用委托时较为复杂。
- 问题实例1:有些人既是经理,又是学生,比如某位在读MBA的老总;但是这都是角色的一种,可以认为人拥有角色(在人和角色之间搭建桥梁)。
- 问题实例2:汽车的品牌和汽车的挡位类型无关,独立变化,互不影响。
- AbstractCar和Transmission之间是关联关系;两者之间独立变化,实现抽象和具体实现的分离
// 实例代码demo
//抽象化角色类//角色化类
abstract class AbstractCar {
protected Transmission gear;
public abstract void run();
public void setTransmission(Transmission gear) {
this.gear = gear;
}
}
//改进(修正)抽象化角色类:按品牌分,BMW牌车
class BMWCar extends AbstractCar {
@Override
public void run() {
gear.gear();
}
}
//BenZCar..等
//抽象变速器
abstract class Transmission {
public abstract void gear();
}
//具体变速器:手动档
class Manual extends Transmission {
@Override
public void gear() {
System.out.println("Manual transmission");
}
}//
//还有自动档类等
//有了变速器和品牌两个维度各自的实现后,可以通过聚合,实现不同品牌不同变速器的车
public class BridgeClient {
public static void main(String[] args) {
Transmission manual = new Manual();
AbstractCar bmw = new BMWCar();
bmw.setTransmission(manual);
bmw.run();
Transmission manual = new Manual();
AbstractCar benz = new BenZCar();
benz.setTransmission(manual);
benz.run();
}
}
// 是否违反针对接口编程原则和依赖倒置原则?
- Bridge(桥梁)模式整体结构
- 抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立变化
//抽象化角色类
public abstract class Abstraction {
protected Implementor impl;
public Abstraction(Implementor impl) {
this.impl = impl;
}
//示例方法
public abstract void operation();
}
//修正抽象化角色类
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor impl) {
super(impl);
}
public void operation() {
impl.operationImpl();
}
//其他的操作方法
public void otherOperation() {
}
}
//实现化角色类
public abstract class Implementor {
/**
* 示例方法,实现抽象部分需要的某些具体功能
*/
public abstract void operationImpl();
}
//具体实现化角色类
public class ConcreteImplementorA extends Implementor {
public void operationImpl() {
//具体操作
}
}
public class ConcreteImplementorB extends Implementor {
public void operationImpl() {
//具体操作
}
}
- 设计理想:需求变化的时候,尽可能减少修改代码就可以满足新的需求。
设计原则-封装可变性
-
内容:将代码容易变化的部分封装起来,将其和代码不容易变化的部分独立开来
-
目标:实现可变性和不可变性的分离,将可变性因素映射为同一个抽象类的不同子类,分块封装可变因素,这也是对开闭原则的最好实现
-
举两个例子:
- 蜡笔的型号和颜色不能采用桥梁模式,蜡笔的颜色和蜡笔的型号在出厂就已经确定,两者不是可独立的模块
- 但是毛笔的型号和颜料之间就可以采用桥梁模式,注意毛笔和颜料是可以分离的,可以独立选择,类型是可以发生变化的
- 最后强调一点:桥接模式中的桥接是单向的,即只能是抽象部分的对象去使用具体实现部分的对象,反之不可
设计模式-Adapter(适配器模式)
- 问题:在已有“鸭子”类的情况下,我们增加一种“鸟”类,要求复用“鸭子”类的代码,存在新老代码接口不一致的现象,老代码修改也没有那么方便,可能存在很多问题(违反“开-闭原则”)
- 方法:应用(对象)适配器模式实现接口转换
- 定义1-接口转换:将一个类的接口转换为客户希望的另外一个接口,使得原来由于接口不兼容的类之间可以在一起工作,通过新建类和对象组合的方式,复用“鸭子”类的功能,满足“鸟”类的调用要求,适配器和鸭子为**“关联”关系**
- 定义2-重新包装,改变接口:类适配器,适配器和“鸭子”类之间为继承关系
// 实现代码demo
// 已存在的、具有特殊功能、但不符合我们既有的标准接口的类
class Adaptee {
public void specificRequest() {
System.out.println("被适配类具有 特殊功能...");
}
}
// 目标接口,或称为标准接口
interface Target {
public void request();
}
// 具体目标类,只提供普通功能
class ConcreteTarget implements Target {
public void request() {
System.out.println("普通类 具有 普通功能...");
}
}
// 适配器类,继承了被适配类,同时实现标准接口
class Adapter extends Adaptee implements Target {
public void request() {
super.specificRequest();
}
}
// 测试类
public class Client {
public static void main(String[] args) {
// 使用普通功能类
Target concreteTarget = new ConcreteTarget();
concreteTarget.request();
// 使用特殊功能类,即适配类
Target adapter = new Adapter();
adapter.request();
}
}
设计模式-装饰者模式(油漆工)
- 问题提出:假设有一个饮料店,抽象一个饮料类,如果加入不同调料的饮料都有一个名称,那么数量(种类)将会很多,对应的价格计算也会有很多结果,不方便采用继承的方式实现(当价格变动,调料增加时)
- 方法:以饮料为主题,运行时用“调料”来装饰饮料
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2iyO75iU-1655602874779)(https://cdn.jsdelivr.net/gh/cccccwwl/images@main/img/202206180114993.png)]
// 代码实现demo
public abstract class Beverage {
protected String description;
public String getDescription() {
return description;
}
public abstract double cost();
}
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "Dark Roast Coffee";
}
public double cost() {
return 0.99;
}
}
public abstract class CondimentDecorator extends Beverage {
protected Beverage beverage;
}
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
public double cost() {
return 0.20 + beverage.cost();
}
}
public class StarbuzzCoffee {
public static void main(String args[]) {
Beverage beverage = new Decaf();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
Beverage beverage1 = new Decaf();
beverage1 = new Milk(beverage1);
beverage1 = new Mocha(beverage1);
System.out.println(beverage1.getDescription() + " $" + beverage1.cost());
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Milk(beverage2);
System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
}
}
- 定义装饰者模式的一般情况:
-
抽象构件(Component)角色:组件对象的接口,可以给这些对象动态地添加职责
-
具体构件(Concrete Component)角色:实现组件对象接口,通常就是被装饰者装饰的原始对象,也就是可以给这个对象添加职责。
-
装饰者(Decorator)角色:所有装饰者的抽象父类,需要定义一个与组件接口一致的接口,并持有一个Component对象,其实就是持有一个被装饰的对象。
-
具体装饰者(Concrete Decorator)角色:实际的装饰者对象,实现具体要向被装饰对象添加的功能。
-
现在来看一个实例巩固一下:
-
问题:假设你需要打印发票 sales ticket , 发票有抬头、正文和脚注,发票抬头可以是企事业单位,发票号等等,脚注也是一样,可能有很多不同种类的脚注需要打印。如果发票格式固定那也就没必要继续讨论了,现在的问题是,不同的客户需要的发票或者收据的抬头或脚注,他们需要的条目是不一样的,有的需要著明单位,有的只需要发票号,但是脚注需要开票人, 等等,对你来说跟现在的 Web 系统一样,客户的要求是动态;不过发票的正文是不会变化的,是固定的
-
UML图示
// 如果你的发票格式为:
SalesTicket Header1
SalesTicket Body
SalesTicket Footer1
// 那么你可以这样去创建对象:
new Header1(new Footer1(new SalesTicket()));
// 如果你的发票格式为:
SalesTicket Header1
SalesTicket Header2
SalesTicket Body
SalesTicket Footer1
// 那么你可以这样去创建对象:
new Header1(new Header2(new Footer1(new SalesTicket())));
-
思考关联性:
- 给对象增加一些新的功能而不是给整个类增加新功能的时候,不建议采用继承(不灵活,可能会导致类爆炸),建议采用装饰者
- 装饰必须与被装饰的组件继承自同一个接口,装饰会将用户的请求转发给相应的组件(即调用相关的方法)
-
探索本质:
- 动态组合:动态地为对象增加新的职责,灵活性比继承方式要高
- 建立在继承的基础之上,可以在不改变原有对象的基础之上,将新功能附加到对象上,扩展原有对象的功能,这些功能在运行时决定。动态运行
- 松耦合,符合开闭原则
设计模式-Observer(观察者模式)
-
问题提出:气象站需要获取当天的数据并在不同的布告板上显示关于当天天气的有关信息,利用气象站已有的WeatherData对象取得数据,请给出measurementsChanged的实现
public void measurementsChanged() { float temp = getTemperature(); float humidity = getHumidity(); float pressure = getPressure(); currentConditionDisplay.update(temp, humidity, pressure); statisticsDisplay.update(temp, humidity, pressure); forecastDisplay.update(temp, humidity, pressure); } // currentConditionDisplay,statisticsDisplay,forecastDisplay 是三个布告板对象,在WeatherData类中维护着具体的currentConditionDisplay、 statisticsDisplay和 forecastDisplay
-
我们发现这种实现方法没有做到“针对接口编程”,违反开闭原则
-
当然,报纸和杂志的订阅者和报社之间也是这样的关系
-
-
核心:观察者模式 = 订阅 + 广播
- 观察者模式把多个订阅者称为观察者:Observer;多个观察者观察的对象被称为目标:Subject
-
分析:一个报纸的对象会有多个订阅者前来订阅,报纸出版的时候报纸对象发生了改变,需要通知所有的订阅者对象发生变化,执行相应功能,使得观察者状态和目标状态保持一致
-
常见的观察者模式:发布-订阅(Publish/Subscribe)模式;模型-视图(Model/View)模式;源-监听器(Source/Listener)模式;从属者(Dependents)模式
-
定义:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象/目标。这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己
- 抽象主题/目标角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类或接口来实现。(左上)
- 抽象观察者角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。(右上)
- 具体主题/目标角色:在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。(左下)
- 具体观察者角色:该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。(右下)
// 实现demo // 典型的抽象目标类代码 import java.util.*; public abstract class Subject { //定义一个观察者集合用于存储所有观察者对象 protected ArrayList<Observer> observers = new ArrayList(); //注册方法,用于向观察者集合中增加一个观察者 public void attach(Observer observer) { observers.add(observer); } //注销方法,用于在观察者集合中删除一个观察者 public void detach(Observer observer) { observers.remove(observer); } //声明抽象通知方法 public abstract void notifyAllObservers(); } // 典型的具体目标类代码 public class ConcreteSubject extends Subject { //实现通知方法 public void notifyAllObservers() { //遍历观察者集合,调用每一个观察者的响应方法 for (Object obs : observers) { ((Observer) obs).update(); } } } // 典型的抽象观察者代码 public interface Observer { //声明响应方法 public void update(); } // 典型的具体观察者代码 public class ConcreteObserver implements Observer { //实现响应方法 public void update() { //具体响应代码 } } // public class Main { public static void main(String[] args) { //客户端代码: Subject subject = new ConcreteSubject(); Observer observer = new ConcreteObserver(); subject.attach(observer); subject.notifyAllObservers(); } }
-
本质:触发联动:当目标对象发生变化时,会联动调用这些观察者的更新方法;联动是动态的,在程序运行期间,可以通过注册和取消注册来动态地控制观察者;实现目标对象和观察者对象的解耦
-
适用性:
- 一个模型的两个方面,其中有一个方面会依赖另一个方面,将二者独立封装于对象之中可以使得他们被独立的改变和复用
- 当一个对象的改变时会引起其他对象的更新,但是不知道有多少对象需要被改变或者需要改变的对象是谁,就可以采用观察者模式解决这个问题
-
问题实例1:在某多人联机对战游戏中,多个玩家可以加入同一战队组成联盟,当战队中的某一成员受到敌人攻击时将给所有其他盟友发送通知,盟友收到通知后将做出响应。核心:联动
-
联动过程:联盟成员受到攻击 → {\rightarrow} → 发送通知给盟友 → {\rightarrow} → 盟友做出响应
-
UML图
(1) AllyControlCenter:指挥部(战队控制中心)类,充当抽象目标类
(2) ConcreteAllyControlCenter:具体指挥部类,充当具体目标类
(3) Observer:抽象观察者类
(4) Player:战队成员类,充当具体观察者类
(5) Client:客户端测试类 -
结果分析:两次对象之间的联动,触发链: player.beAttacked() → {\rightarrow} → allyControlCenter.notifyObserver() → {\rightarrow} → player.help()
设计模式-Chain of Responsibility(责任链模式/职责链模式)
-
定义:责任链模式/职责链模式属于对象的行为型模式,避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止,客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上,将请求的发送者和请求的处理者解耦
-
举个栗子:奖学金的层级审批,各级审批者都可以处理申请表,他们构成一条处理申请表的链式结构,申请表沿着这条链式结构传递,最常见的职责链为直线型
-
Handler指向自己的关系是聚合关系
-
职责链的角色:Handler(抽象处理者);ConcreteHandler(具体处理者)
// 实现代码demo // 典型的抽象处理者代码 public abstract class Handler { //维持对下家的引用 protected Handler successor; public void setSuccessor(Handler successor) { this.successor = successor; } public abstract void handleRequest(String request); } // 典型的具体处理者代码 public class ConcreteHandler extends Handler { public void handleRequest(String request) { if (请求满足条件) { //处理请求 } else { this.successor.handleRequest(request); //转发请求 } } } // 典型的客户端代码 public class Main { public static void main(String[] args) { //客户端代码: Handler handler1, handler2, handler3; handler1 = new ConcreteHandlerA(); handler2 = new ConcreteHandlerB(); handler3 = new ConcreteHandlerC(); //创建职责链 handler1.setSuccessor(handler2); handler2.setSuccessor(handler3); //发送请求,请求对象通常为自定义类型 handler1.handleRequest("请求对象"); } }
-
-
练习一下:考过的原题
-
UML图
(1) PurchaseRequest:采购单类,充当请求类
(2) Approver:审批者类,充当抽象处理者
(3) Director:主任类,充当具体处理者
(4) VicePresident:副董事长类,充当具体处理者
(5) President:董事长类,充当具体处理者
(6) Congress:董事会类,充当具体处理者
(7) Client:客户端测试类// 代码demo package designpatterns.cor; //经理类:具体处理者 public class Manager extends Approver { public Manager(String name) { super(name); } //具体请求处理方法 public void processRequest(PurchaseRequest request) { if (request.getAmount() < 80000) { System.out.println("经理" + this.name + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); //处理请求 } else { this.successor.processRequest(request); //转发请求 } } } public class Client { public static void main(String[] args) { Approver wjzhang, gyang, jguo, meeting; wjzhang = new Director("张无忌"); gyang = new VicePresident("杨过"); jguo = new President("郭靖"); meeting = new Congress("董事会"); //创建职责链 wjzhang.setSuccessor(gyang); gyang.setSuccessor(jguo); jguo.setSuccessor(meeting); //创建采购单 PurchaseRequest pr1 = new PurchaseRequest(45000, 10001, "购买倚天剑"); wjzhang.processRequest(pr1); PurchaseRequest pr2 = new PurchaseRequest(60000, 10002, "购买《葵花宝典》"); wjzhang.processRequest(pr2); PurchaseRequest pr3 = new PurchaseRequest(160000, 10003, "购买《金刚经》"); wjzhang.processRequest(pr3); } }
-
-
模式的优缺点:
- 优点:客户端对象无需知道是哪一个对象处理了其请求,降低了系统的耦合度;简化对象直接的相互连接;对象的职责分配更加灵活;增加一个新的具体请求处理者时无需修改原有系统的代码,仅需在客户端重新构建责任链即可
- 缺点:不能保证请求一定会被处理,可能不被处理,对于较长的职责链,链式搜索系统性能会受到一定影响,代码调试不方便;循环调用的问题
-
模式适用环境:未知哪一个对象会接受请求,向多个对象中提交一个请求;动态地指定一组对象处理请求;多个对象都可处理请求,具体哪个对象会处理该请求待运行时刻才会确定
设计模式-Strategy(策略模式)
- 定义:策略模式属于对象的行为模式
- 目标:平滑地处理不同算法间的切换,将一组算法封装到具有共同接口的独立的类中,在相互切换(替换)的时候不影响客户端的变化
- 模式思想:定义算法族,分别封装起来,算法之间可以互相替换,算法和使用算法的客户之间独立变化,但是什么时候使用是客户端决定的;算法完成的是相同行为的不同实现,以相同的方式调用算法,减少算法类和使用算法类的客户之间的耦合
- 设计原则:组合优先原则,开闭原则,封装可变性;定义策略接口,实现“针对接口编程”
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CPFkZ5LP-1655602874779)(https://cdn.jsdelivr.net/gh/cccccwwl/images@main/img/202206182106559.png)]
- Strategy(策略):定义所有支持的算法的公共接口
- ConcreteStrategy(具体策略):实现具体算法
- Context(上下文):用一个ConcreteStrategy对象来配置;维护一个对Strategy对象的引用;对策略进行二次封装,目的是避免高层模块对策略的直接调用;上下文包含了策略的方式
// 实现代码demo
public class Context {
//持有一个具体策略的对象
private Strategy strategy;
/**
* 构造函数,传入一个具体策略对象
*
* @param strategy 具体策略对象
*/
public Context(Strategy strategy) {
this.strategy = strategy;
}
/**
* 策略方法
*/
public void contextInterface() {
strategy.strategyInterface();
}
}
//抽象策略类
public interface Strategy {
//* 策略方法
public void strategyInterface();
}
//具体策略类
public class ConcreteStrategyA implements Strategy {
public void strategyInterface() {
//相关的业务
}
}
public class ConcreteStrategyB implements Strategy {
public void strategyInterface() {
//相关的业务
}
}
-
实例1:假设有不同的加密算法可以对数据进行加密,需要根据需求切换加密算法
// 代码demo
//封装类:上下文
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public String encrypt(String s) {
return this.strategy.encrypt(s);
}
}
//抽象策略(加密接口)
public interface Strategy {
//加密
public String encrypt(String s);
}
//具体策略
public class MD5Strategy implements Strategy {
public String encrypt(String s) {
return "" 执行MD5加密”+s;
}
}
public class SHA1Strategy implements Strategy {
public String encrypt(String s) {
return "执行SHA1加密”+s;
}
}
//客户端调用
public class MainClass {
public static void main(String[] args) {
String s =“test”;
String es;//密文
Context context = null;
context = new Context(new MD5Strategy());
context.encrypt(s);
//context=new Context(new SHA1Strategy());
//es=context.encrypt(s);
}
}
-
实例2:A店除了正常日不打折,在节假日会推出满300减100,全场8折等活动
// 代码实现demo // 定义抽象策略角色(Strategy):现金收费接口 public interface CashSuper { public double acceptCash(double money); } // 定义具体策略角色(Concrete Strategy):正常收费、每个节日具体的促销活动….. public class CashNormal implements CashSuper { public double acceptCash(double money) { return money; } } public class CashRebate implements CashSuper { private double moneyRebate = 1; public CashRebate(double moneyRebate) { this.moneyRebate = moneyRebate; //如八折时,传入0.8 } public double acceptCash(double money) { return money * moneyRebate; } } // 定义环境角色(Context):用于连接上下文,即把促销活动推销给客户 public class CashContext { // 调用具体的打折和不打折方法 private CashSuper cs = null; public CashContext(CashSuper cs) { this.cs = cs; } public double getResult(double money) { return cs.acceptCash(money); } } // 客户端调用 public class SalesMan { public static void main(String[] args) { CashContext mSalesMan; // 平常不打折,消费了1000 CashSuper cashWay = new CashNormal(); mSalesMan = new CashContext(cashWay); double normalResult = mSalesMan.getResult(1000); System.out.println("平常:" + normalResult); // 打0.8折 cashWay = new CashRebate(0.8); mSalesMan = new CashContext(cashWay); double rebateResult = mSalesMan.getResult(1000); System.out.println("打折:" + rebateResult); } }
设计原则-里氏代换原则
-
定义1:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型(可替换原则)
- 定义2:所有引用基类的地方必须能透明地使用其子类的对象;将父类替换成子类,外界没有任何察觉
- 定义3:子类可以扩展父类的功能,但不能改变父类原有的功能
-
核心:凡是父类适用的地方子类应当也适用,继承只有满足里氏代换原则才是合理的
-
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象
-
含义:
- 子类实现父类的抽象方法,不能覆盖父类的非抽象方法,但是子类可以增加自己的特有方法
- 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松;防止方法的调用者传入的参数不能被处理
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格;防止方法的调用者不能处理
// 方法的前置条件要比父类方法的输入参数更宽松 import java.util.HashMap; public class Father { public void func(HashMap m) { System.out.println("执行父类..."); } } import java.util.HashMap; public class Son extends Father { public void func(Map m) { //方法的形参比父类的更宽松 System.out.println("执行子类..."); } } import java.util.HashMap; public class Client { public static void main(String[] args) { Father f = new Son(); //引用基类的地方能透明地使用其子类的对象。 HashMap h = new HashMap(); f.func(h); } } // 运行结果:执行父类...
-
问题提出:
鸟一般都会飞行,如燕子的飞行速度大概是每小时 120 千米。但是新西兰的几维鸟由于翅膀退化无法飞行。假如要设计一个实例,计算这两种鸟飞行 300 千米要花费的时间。
显然,拿燕子来测试这段代码,结果正确,能计算出所需要的时间;但拿几维鸟来测试,结果会发生“除零异常”或是“无穷大”,明显不符合预期
// 代码demo class Bird { double flySpeed; public void setSpeed(double speed) { flySpeed = speed; } public double getFlyTime(double distance) { return (distance / flySpeed); } } //几维鸟类 class BrownKiwi extends Bird { public void setSpeed(double speed) { flySpeed = 0; } } // 客户端代码 public class LSPtest { public static void main(String[] args) { Bird bird1 = new Swallow(); Bird bird2 = new BrownKiwi(); bird1.setSpeed(120); bird2.setSpeed(120); System.out.println("如果飞行300公里:"); try { System.out.println("燕子将飞行" + bird1.getFlyTime(300) + "小时."); System.out.println("几维鸟将飞行" + bird2.getFlyTime(300) + "小时。"); } catch (Exception err) { System.out.println("发生错误了!"); } } }
-
我们重构上述UML图及其代码:
-
LSP里氏代换原则给出的解决方案:
- 如果两个具体的类A,B之间的关系违反了LSP的设计,(假设是从B到A的继承关系)那么根据具体的情况可以在下面的两种重构方案中选择一种:
- 创建一个新的抽象类C,作为两个具体类的超类,将A,B的共同行为移动到C中来解决问题
- 从B到A的继承关系改为委派关系
- 如果两个具体的类A,B之间的关系违反了LSP的设计,(假设是从B到A的继承关系)那么根据具体的情况可以在下面的两种重构方案中选择一种:
设计原则-接口隔离原则(ISP)
-
定义:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口
-
目标:角色的合理划分,一个接口应该简单的代表一个角色(定义明确),不要把没什么联系的接口都放在一起;接口方法尽量少;一个类对于另一个类的接口依赖应该尽量少
-
核心:提高内聚
-
举个栗子:全文搜索引擎
上述方法均放在一个接口内,这是不好的,我们应该将接口进行分割
-
接口隔离原则(ISP)和迪米特法则
- 迪米特法则要求尽量限制通信的广度和深度
- 对接口进行分割,使其最小化,避免对客户提供不需要的服务,符合迪米特法则;体现了高内聚,低耦合
设计原则-单一职责原则(SRP)
-
定义:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因;用于控制类的粒度大小
-
问题提出:类T负责两个不同的职责:职责P1、职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原来运行的职责P2功能发生故障
-
解决方案:分别建立两个类完成对应的功能;遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险
-
举个栗子:
-
存在的问题:CustomerDataChart类承担了太多的职责,既包含与数据库相关的方法,又包含与图表生成和显示相关的方法。如果在其他类中也需要连接数据库或者使用findCustomers()方法查询客户信息,则难以实现代码的重用。
-
方案:无论是修改数据库连接方式还是修改图表显示方式都需要修改该类,它不止一个引起它变化的原因,违背了单一职责原则。因此需要对该类进行拆分,使其满足单一职责原则
-
-
单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离
- 单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建
设计原则-针对接口编程
- 定义:要针对接口编程,不要针对实现编程
- 创建一个新的抽象类C,作为两个具体类的超类,将A,B的共同行为移动到C中来解决问题
- 从B到A的继承关系改为委派关系
设计原则-接口隔离原则(ISP)
-
定义:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口
-
目标:角色的合理划分,一个接口应该简单的代表一个角色(定义明确),不要把没什么联系的接口都放在一起;接口方法尽量少;一个类对于另一个类的接口依赖应该尽量少
-
核心:提高内聚
-
举个栗子:全文搜索引擎
上述方法均放在一个接口内,这是不好的,我们应该将接口进行分割
-
接口隔离原则(ISP)和迪米特法则
- 迪米特法则要求尽量限制通信的广度和深度
- 对接口进行分割,使其最小化,避免对客户提供不需要的服务,符合迪米特法则;体现了高内聚,低耦合
设计原则-单一职责原则(SRP)
-
定义:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因;用于控制类的粒度大小
-
问题提出:类T负责两个不同的职责:职责P1、职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原来运行的职责P2功能发生故障
-
解决方案:分别建立两个类完成对应的功能;遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险
-
举个栗子:
-
存在的问题:CustomerDataChart类承担了太多的职责,既包含与数据库相关的方法,又包含与图表生成和显示相关的方法。如果在其他类中也需要连接数据库或者使用findCustomers()方法查询客户信息,则难以实现代码的重用。
-
方案:无论是修改数据库连接方式还是修改图表显示方式都需要修改该类,它不止一个引起它变化的原因,违背了单一职责原则。因此需要对该类进行拆分,使其满足单一职责原则
-
-
单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离
- 单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建
设计原则-针对接口编程
- 定义:要针对接口编程,不要针对实现编程
- 建议:变量应该声明为抽象类;不要继承自非抽象类;不要重载父类的非抽象方法