七大原则
-
单一职责原则 职责要单一不能将太多的职责放在一个类中
-
开闭原则 软件实体对扩展是开放的,但对修改是关闭的
-
里氏代换原则 一个可以接受基类对象的地方必然可以接受子类
-
依赖倒转原则 要针对抽象层编程,而不要针对具体类编程
-
接口隔离原则 使用多个专门的接口来取代一个统一的接口
-
合成复用原则 复用功能时,优先使用组合和聚合关联关系,尽量不要使用继承
-
迪米特法则 一个软件实体对其他软件实体的应用越少越好,或者两个类不必彼此直接通信,就不应当发生直接的相互作用,而是通过引入一个第三者发生间接交互。
设计模式
定义:设计模式是一套被反复使用被多数人知晓的经过分类编目的,代码设计经验的总结。
基本要素:模式名称,问题,目的,解决方案,效果实例代码,和相关设计模式。
创建型模式
工厂模式
简单工厂模式
定义:定义了一个类,用这个类来封装实例化对象的类
问题:类的创建依赖工厂类,如果想要扩展新的类,就必需对工厂类进行修改
解决方法:增加新的功能,去增加新的工厂类就可以,使用工厂方法模式
工厂方法模式
定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
解决了工厂模式问题,如果想要,只需要再增加一个实现类,就行。
问题:如果我要A产品,只能去找到生成A产品的工厂才能去得到,无法达到用户说出那个产品的需求,工厂自会安排的效果
解决方法:
把工厂类抽象为接口,根据用户的需求,即对应的产品,去找到对应的参数,这就是简单工厂模式
抽象工厂模式
角色:
AbstractFactory 抽象工厂
WindowsFactory 等具体工厂
Text,Button 抽象产品
WindowsText等具体产品
如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。
抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品。
如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。
在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。
建造者模式
定义:该模式允许将复杂的对象构建步骤与表示方式相分离,通过指定复杂对象的类型和内容就可以构建他们
角色:
抽象建造者类 Builder
public abstract class ComputerBuilder {
protected Computer computer=new Computer();public abstract void buildType();public abstract void buildRam();
public abstract void buildHdd();
public abstract void buildCpu();
public abstract Computer getComputer(); }
具体建造者类 ConcreteBuilder
public class ConcreteBuilder extends ComputerBuilder{
@Override
public void buildType(){computer.setType("Desktop");}
@Override
public void buildRam() {computer.setRam("16GB");}@Override
public void buildHdd() {computer.setHdd("2TB");}@Overridepublic void buildCpu() {computer.setCpu("Desktop cpu 11th gen");
} @Override
public Computer getComputer() {return computer;} }
产品角色 Product
public class Computer {
private String type;
private String ram;
private String hdd;
private String cpu;
public String getType() { return type; }
public String getRam() { return ram; }
public String getHdd() { return hdd; }
public String getCpu() { return cpu; }
public void setType(String type) { this.type = type; }
public void setRam(String ram) { this.ram = ram; }
public void setHdd(String hdd) { this.hdd = hdd; }
public void setCpu(String cpu) { this.cpu = cpu; } }
指挥者类 Director
public class ComputerAssembleDirector {private ComputerBuilder computerBuilder;public ComputerAssembleDirector(ComputerBuilder computerBuilder) {this.computerBuilder = computerBuilder;}public Computer assemble(){computerBuilder.buildType();computerBuilder.buildRam();computerBuilder.buildHdd();computerBuilder.buildCpu();return computerBuilder.getComputer();}
}
类图:
优点:
-
客户端不必知道产品内部细节,本身与本身创建过程解耦, 相同的创建过程创建不同的产品对象。
-
具体建造者互相独立,与其他建造者无关,使用不同的建造者得到不同的对象
-
符合开闭原则
-
精细的控制产品的创建过程
缺点:
该模式所创建的产品一般具有较多的共同点,产品之间如果差异性很大则,不适合使用该模式。
原型模式
定义:原型模式用原型实例创建对象的种类,并且通过复制这些原型创建新的对象。
工作原理:将一个对象通过请求原型对象传给那个要发动创建的对象,该对象通过请求原型对象复制原型来创建的过程。
角色:
抽象原型类
具体原型类
浅克隆
浅克隆中,被复制对象的所有普通成员变量都具有与原来对象相同的值,而所有对象对其他对象的引用仍然指向原来的对象,也就是只考虑当前对象,不考虑成员对象,浅克隆时要使用Object的Clone接口
这就是浅克隆的核心
public class Many implements Cloneable{public Object clone() {try {return super.clone();} catch (CloneNotSupportedException e) {throw null;}}
}
深克隆
深克隆顾名思义,这两个对象是完全不同的对象,对其他对象的引用也会改变
深克隆 不会用Clone()接口,而是使用序列化传输,进行流的转换。
public class Many implements Serializable {public Object deppClone() throws IOException, ClassNotFoundException {//对象写入流中ByteArrayOutputStream outputStream = new ByteArrayOutputStream();ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);objectOutputStream.writeObject(this);// 对象从流中取出ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);return objectInputStream.readObject();}
}
优点 :
-
对象比较复杂时,该模式简化对象的创建过程,提高效率
-
动态增加和减少产品类
-
简化的创建结构,无需用专门的工厂类去创建
-
深克隆的对象保持对象当前的状态,以便需要的时候使用
缺点:
-
需要为每个类配备一个克隆方法,没有创建的类简单,但是对于已经创建的类去改造比较麻烦,所以违反了开闭原则
单例模式
定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,提供全局访问的方法。
要点:
-
一个类只有一个实例
-
必须自行创建这个实例
-
必须自行向整个系统提供这个实例
饿汉式单例
public class Many {private static final Many many=new Many();private Many(){}public static Many getInstance(){return many;}
}
懒汉式单例
同步化机制,处理多线程环境
public class Many {private static Many instance=null;private Many(){};synchronized public static Many getInstance(){return (instance==null)?instance=new Many():instance;}双重检查 synchronized+instance==null 确保只有一个单例生成
}
结构型模式
适配器模式
定义:将一个类的接口转换成用户希望的另一个接口,原本不兼容的类可以一起工作,无需修改现有事物的内部。
角色:
Target 目标抽象类 抽象类或接口,具体类
Adapter 适配器类 适配类可以调用另一个接口,对Adapter 和Target进行适配,是适配器的核心。类适配器中实现Target接口并继承Adaptee达到效果。 java只能单一继承,对象适配器中 继承Target并关联Adapptee达到效果
Adaptee(适配者类) 被适配的角色,定义了一个已经存在的接口,并且需要适配。
类适配器
对象适配器
对象适配器与类适配器类主要的区别就是adapter的区别
public class Adapter implements Target {private Adaptee adaptee;@Overridepublic void request() {adaptee.specificRequest();}
}
优点:
-
将目标类和适配器类解耦,引入适配器类,而不用去修改原有代码
-
增加了类的透明性和复用性,具体实现封装在适配器类,对客户端透明,且提高了复用性
-
灵活性和扩展性都非常好,符合开闭原则
缺点:
桥接模式
定义:抽象部分与他的实现部分分离,使他们都可以独立的变化。
桥接模式存在一条连接两个继承等级的桥
角色:
-
Abstraction (抽象类)
-
RefinedAbstraction(扩充抽象类)
-
Implementor(实现类接口)
-
ConcreteImplementor(具体实现类)
优点:
-
分离抽象化及其实现
-
是比多继承方案更好的方案
-
提高了系统的可扩展性
-
实现细节对客户透明,可以对用户隐藏实现细节,用户在使用时不需要关心实现,在抽象层通过聚合关联关系完成封装和对象的封装。
缺点:
-
桥接模式的引入会增加系统的理解和设计难度,由于聚合
关联关系是建立在抽象层,需要针对抽象进行设计
-
需要正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。
组合模式
定义:
组合多个对象形成树形结构以表示部分-整体的结构层次,组合模式分为单个对象(叶子对象)和组合对象(即容器对象)
核心:
引入一个抽象类,他即是叶子对象的父类,也是容器对象的父类
类图:
角色:
Component(抽象构件) 接口/抽象类 定义所有子类的共有行为的申明和实现
Leaf叶子节点,表示叶子节点对象,叶子节点没有子节点,只是实现抽象构件中的部分行为
Composite(容器构件) 表示容器节点对象,包含子节点,其子节点可以是叶子节点也可是容器节点,提供一个集合去存放子节点,实现了在抽象构件中定义的行为,访问和管理子构件的行为
就像文件夹中可以存放文件夹也可以存放文件。
安全组合模式,透明组合模式
安全组合模式
由此就可以看出组合模式的具体实现,可以观察到这部分实现容器构件的一些特殊的方法,在抽象构件,叶子节点中都没有出现,因为这些方法在他们这里出现也没有意义没有任何东西,反而是不够安全的,运行阶段调用这些方法反而是空的,所以上个类图展现的模式是不安全的,叫透明组合模式,改进后的叫安全组合模式。
透明组合模式类图
安全组合模式类图
优点:
-
可以清楚的定义分层次的复杂对象,表示全部或部分层次
-
客户端调用时不必关心自己处理的是单个对象或者组合结构
-
叶子对象可以被组合成更复杂的结构,容器对象也可以被组合,可以形成更加复杂的树形结构
缺点:
-
设计变得抽象,对象的业务规则如果复杂挑战性比较da
-
增加新构件有可能会出现一些问题,很难对容器中的构件类型进行限制。
装饰模式
定义:
动态地给一个对象增加一些额外的职责,就增加对象来说,装饰模式比生成子类更为灵活
类图:
角色:
Commponent(抽象构件) 定义对象的接口,可以给这些对象动态的增加职责,抽象装饰类和具体构件的共同父类,一致处理未被装饰的对象和装饰之后的对象
public abstract class Commponent { abstract void operation(); }
ConcreteComonent(具体构件) 具体构件实现了抽象构件中申明的方法,装饰器可以动态的添加功能
public class ConncreteCommpent extends Commponent{
@Override void operation() { System.out.println("operation"); } }
Decorator(抽象装饰类) 用于添加职责,具体职责在子类中实现。
public class Decorator {private Commponent commponent;
public Decorator(Commponent commponent) { this.commponent = commponent; }
public void operation(){ commponent.operation(); } }
ConcereDecorator(具体装饰类) 具体添加职责的类
public class ConncreteDecorator extends Decorator{
public ConncreteDecorator(Commponent commponent) { super(commponent); }@Override public void operation() { super.operation(); addBehavior(); // 增加功能 }public void addBehavior(){ System.out.println("addBehavior"); } }
优点:
-
该模式与继承都是扩展对象的功能,不过该模式更加灵活
-
动态方式扩展对象的功能
-
具体装饰类与被装饰类之间的排列组合创造出不同的装饰器
-
具体构件类与具体装饰类可以独立变化,使用时再进行组合,符合开闭原则
缺点:
透明装饰模式
透明装饰模式就是你的定义了一个具体构件你可去放入任何的装饰器,而不是一对一的关系,是一对多
半透明装饰模式
半透明装饰模式与透明装饰模式相反,我这个具体构件只能由这一类的装饰器来执行,其他装饰器不可以。
外观模式
引入一个外观角色来简化客户端与子系统之间的操作,为复杂的子系统调用提供一个统一的接口,使子系统与客户端的耦合度降低。
划分若干子系统,降低系统复杂性,体现单一职责原则
引入外观类,降低系统复杂度,降低客户端与系统的耦合度,体现迪米特原则
类图:
角色:
Facade外观角色
在外观角色中可以知道相关子系统的功能和责任,在正常情况下他会接受上面发来的请求去转发给子系统来处理。
SubSystem子系统角色
-
每个子系统可以是单独的类或者类的集合,实现子系统的功能
-
每个子系统都可以被直接调用,处理由外观类的请求
上述描述的缺点中提到了如果要增加新的子系统,需要修改外观类,违背了开闭原则,所以再加一层抽象外观类就解决了。
享元模式
当系统中存在大量的相同或者相似对象,该模式通过共享技术实现相同或者相似细粒度的对象复用,节约了内存空间。提供享元池存储已经创造好的对象,并且通过享元工厂类提供享元对象
定义:运用共享技术有效支持大量细粒度对象的复用,要求能够共享的对象必须是细粒度对象,所以又称轻量级模式
享元模式一般结合工厂类模式使用
类图:
角色:
具体享元类 ConcreteFlyweight
抽象享元类 Flyweight
非共享具体享元类 UnsharedConcreteFlyWeight
享元工厂类 FlyweightFactory
代理模式
定义:给摸一个对象提供一个代理,并由代理对象控制原对象的引用
类图:
角色:
抽象主题角色 Subject
声明了真实主题和代理主题得共同接口,任何使用真实主题得场景都可以使用代理主题,客户端需要针对抽象主题进行编程
public abstract class Subject { public abstract void request(); }
Proxy代理主题角色
其内部包含了对真实主题得引用,可以在任何时候操作真实主题对象,在代理主题角色中提供了一个与真实主题相同得接口,以便在任何时候去替代真实主题角色。
public class Proxy extends Subject{
private RealSubject realSubject=new RealSubject();
public void before(){ System.out.println("Ready to send"); }
@Override public void request() { before(); realSubject.request(); after(); }
public void after(){ System.out.println("send success"); } }
RealSubject 真实主题角色
定义了代理角色所代表得真实对象,再该对象中体现了真实得业务操作,客户端也可以通过代理主题角色去调用真实主题角色得方法
public class RealSubject extends Subject{@Override
public void request() { System.out.println("send RealSubject"); } }
静态代理
刚才类图中展示得代码就是静态代理,就是在编译之前已经决定好的。
是由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class 文件就已经存在了。它需要为每个被代理的类都创建一个代理类,代理类和被代理类实现相同的接口或者继承相同的抽象类。
动态代理
由于使用静态代理模式去解决这类问题,真实主题对象事先已经存在,作为其内部得属性,这样有可能会导致其类得数量增加,所以出现了动态代理模式。
是在程序运行时,运用反射机制动态地创建代理对象。代理类并不是在代码编译阶段就确定好的,而是在运行时根据需要动态生成字节码并加载到 JVM 中。
public interface Star { void sing(); void dance(); }
public class BigStar implements Star {
具体对象 public void sing(){ System.out.println("sing"); }
public void dance(){ System.out.println("dance"); } }
动态代理类
public class ProxyUtil {public static Star getStar(BigStar bigStar){Star star = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),new Class[]{Star.class},new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if(method.getName().equals("sing")){System.out.println("before sing");method.invoke(bigStar,args);System.out.println("after sing");}else{System.out.println("before dance");method.invoke(bigStar,args);System.out.println("after dance");}return null;}});return star;}
}
优点:
缺点:
-
增加了代理对象,有些类型得代理模式会导致请求速度变慢
-
代理模式有得实现非常复杂
行为型模式
命令模式
定义:将一个请求封装成一个对象,从而使我们可用不同的请求对客户进行参数化,他将请求发送者与请求接受者解耦,请求发送者通过命令对象来间接引用接收者,可以在不修改源码的情况下将相同的发送者对应不同的接收者,也可以将多个命令组成一个宏命令(执行多个命令),还可以在命令类中提供用来撤销申请的方法
类图:
角色:
Command 抽象命令类
接口或者抽象类,声明执行请求的方法,(调用请求接收者的相关操作)
public abstract class Command {
abstract void execute();}
ConcreteCommand 具体命令类
实现了抽象命令的方法,绑定接收者对象的动作
public class ConcreteCommand extends Command{
private Reciver reciver=new Reciver();@Override void execute()
{ reciver.action(); } }
Invoker 调用者
请求的发送者,命令对象来执行请求
public class invoker {private Command command;
public invoker(Command command){ this.command = command; }
public void setCommand(Command command) { this.command = command; }
public void action(){ command.execute(); } }
Receiver 接收者
接收请求,执行动作
public class Reciver { public void action(){ System.out.println("command received!"); } }
优点:
-
降低系统耦合度,使请求者与接收者之间不存在之间引用,不需要交互,两者之间是多对多的关系。
-
新的命令可以很容易加入到系统中,即增加新的具体命令类,满足开闭原则
缺点:
使用命令模式可能会导致系统中存在过多的命令类,可以设置一个宏命令来解决
宏命令模式与命令模式普通版的区别在于新增了一个特殊的类
public class MarcoCommand extends Command{
private List<Command> commands = new ArrayList<>();
public void add(Command command){ commands.add(command); }
@Override void execute() { commands.forEach(Command::execute); } }
迭代器模式
对聚合对象进行遍历,引入迭代器可以将数据的遍历功能从聚合对象分离出来,聚合对象只负责存储数据,遍历数据需要迭代器完成,符合单一职责要求、
定义:提供一种方法来访问聚合对象,不用保留对象的内部表示
角色:
Itearor 抽象迭代器
定义了访问和遍历元素的接口
public interface TVIterator {
void next();
void previous();boolean isFirst();
boolean isLast();
void setChannel(int i);Object currentChannel(); }
ConcreteItearor 具体迭代器
实现了抽象迭代器的接口,完成聚合对象的遍历,并且跟踪其当前位置
public class ConcreteCommand extends Command{private Reciver reciver=new Reciver();@Overridevoid execute() {reciver.action();}
}
Aggregate 抽象聚合类
存储对象,并创建相应迭代器的接口,创建一个迭代器的对象的方法
public interface TV { TVIterator createIterator(); }
ConcreteAggregate 具体聚合类
实现了相应迭代器的接口,实现了聚合类中的创建迭代器的方法
public class HomeTv implements TV{
Object[] objects={"sumsung","xiaomi","huawei"};@Overridepublic TVIterator createIterator() { return new TVIteratorImpl(objects); } }
优点:
支持以不同的方式遍历聚合对象
简化聚合类,在原有的对象中不需要在自行遍历的方法
在同一个聚合上可以有多个遍历
增加新的聚合类和迭代器类都很方便,满足开闭原则
缺点:
观察者模式
一个对象的改变可能会导致一个或多个其他与之存在依赖关系的对象。
定义:
定义对象一对多依赖关系,每当一个对象状态发生更新时,其相关依赖对象都得到通知,并自动更新
别名:
发布订阅模式,模型视图模式,源监听器模式,从属者模式
类图:
角色:
Subject 目标
public abstract class Subject {
protected List<Observer> list=new ArrayList<>();
public abstract void attach(Observer observer);public abstract void detach(Observer observer);
public abstract void notify1(Observer observer); }
c 具体目标
public class ConcreteSubject extends Subject{@Override
public void attach(Observer observer)
{ list.add(observer); }@Overridepublic void detach(Observer observer){ list.remove(observer); }@Override
public void notify1()
{ list.forEach(Observer::update); } }
Observer观察者
public interface Observer { public void update(); }
ConcreteObserver 具体观察者
public class ConcreteObserver implements Observer{
@Override
public void update() { System.out.println("收到通知"); } }
优点:
实现表示层和数据逻辑层得分离,并定义稳定的消息 更新传递机制,抽象了更新接口
观察者模式支持广播通信,观察目标会像所有注册的观察者发送通知,简化了设计难度
符合开闭原则
缺点:
状态模式
解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。
状态模式将一个对象的状态从该对象分离出来,使其状态灵活变化
定义:
允许一个对象在其内部状态时,去改变他的行为,对象看起来似乎修改了他的类
类图:
角色:
Context 环境类
public class Context {
private State state;
public Context(State state){/** 实例化状态 */ this.state = state; }
/**切换状态 * */ public void setState(State state) { this.state = state; }public void request() { state.handle(); } }
State 抽象状态类
public abstract class State { public abstract void handle(); }
ConcreteState具体状态类
public class ConcreteStateA extends State{
@Overridepublic void handle() { System.out.println("ConcreteStateA"); } }
简单状态模式和可切换状态的状态模式
两者之间的区别就是状态是否改变,简单状态模式,在实例化的时候就确定了状态,无法更改
可切换的状态,可以调用方法切换状态,不是一成不变的
优点:
封装了转换规则,无需使用过长的语句来判断状态的转换和转移,提高了代码的可维护性
枚举可能状态,在枚举状态之前需要确定状态种类
将所有与某个状态的行为放到一个类中,可以方便新增增加新的状态,只需要改变对象的状态
允许状态逻辑与状态对象合为一体
缺点:
策略模式
策略模式用于算法的自由切换和扩展,解决某一个问题的算法簇,允许用户随意选择一个。
策略模式实现了算法定义和算法使用的分离,通过继承和多态的机制实现对算法族的使用和管理
完成任务时可以有不同的策略,可以选择不同的策略来完成任务
Context (环境类)
public class Context {private Strategy strategy; /**切换策略,或者注入策略 * */
public void setStrategy(Strategy strategy) { this.strategy = strategy; }public void algorithm() { strategy.algorithm(); } }
Stragey 策略类
public abstract class Strategy { abstract void algorithm(); }
具体策略类 ConcreteStragey
public class ConncreteStragetyA extends Strategy{@Override void algorithm() { System.out.println("策略A"); } }
与状态模式的区别
从他们两者的基本类图看出来很相似大差不差,首先我个人理解的一点是状态模式不断切换状态,方法的结果会随之改变,但是你的策略模式无论采用什么策略最后的目的都是一致的,本身两种模式是为了解决不同问题设计的。