常用的遵循原则:
1:单一职责原则(SRP)就一个类而言,应该仅有一个引起它变化的原因。
2:里氏代换原则(LSP)子类型必须能够替代掉他们的父类型。
3:依赖倒转原则 A: 高层模块不应该依赖底层模块。两个都应该抽象依赖。
B: 抽象不应该依赖细节。细节应该依赖抽象。
4:开放封闭原则(OPC) 软件实体(类,模块,函数等等)应该可以扩展,但是不可以修改。
5:迪米特法则(LoD)也叫最少知道原则。如果两个类不必彼此直接通信,那么这两个类就不应当发生相互作用。如果其中一个类需要调用另一个类的方法时可以通过第三方转发这个调用。
6:合成/聚合复用原则(CARP)尽量使用合成/聚合,尽量不要是要使用继承。
(聚合表示一种弱的‘拥有关系’,体现的是A对象可以包含B对象,但B对象不是A对象的一部分;合成则是一种强拥有关系,体现了严格的强拥有关系,部分和整体的生命周期一样)
我们将现有常用的设计模式可以分为三个类别
分别定义四个算法类 以加法类为例 (结合后面讲到的策略模式)
实体类
客户端调用类
简单工厂模式有一个弊端违背了开放封闭原则。 比如我们要增加一个平方运算,就必须先去改造工厂类,增加case语句判断,简单工厂类的优点是工厂类中包含了必要的逻辑判断,客户端只需要把“+”给工厂而不用再关心其他操作,但是当我们再增加一种算法时我们就要修改原有的工厂类,这不但扩展开放了,对修改也开放了。
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
先构建一个工厂接口然后加减乘除次方分别构建具体工厂去实现这个接口。
客户端实现
仔细观察就会发现,工厂方法模式实现时,客户端需要决定实例化那个工厂来实现运算类,选择判断问题依然存在,也就是说工厂方法把简单工厂的逻辑判断移到了客户端代码中,你想要增加功能由改工厂类转移到了修改客户端! 都有各自的应用场景但不是最完美的。
提供一个创建一系列相关或相互依赖的接口,而无需指定他么具体的类。
工厂不在生产单一产品,同一个品牌类型的一系列产品都由一个工厂去生产。这样要是用户想要A工厂的产品A1和B工厂的产品B2 可以自由组合使用,也可以用户把A工厂系列统一切换生产线到B工厂系列。
抽象工厂可以配合反射技术+配置文件来使用,把需要判断变化的部分放到运行期,比如链接不同数据库的不同配置和不同的工厂类。
总结
简单工厂: 在类对象的请求者与类之间加一层去处理实例化,并通过分支语句来决定如何实例化;优点:解耦,能应对需求变更;缺点:扩展略麻烦,需要修改工厂类
工厂模式:每个工厂只专注一种对象的实例化;优点:解耦,能应对需求变更,扩展不需要修改旧的类;缺点:每次扩展的编码量提升,需要对应多写一个工厂类
抽象工厂:每个工厂负责同一系列的多种对象实例化;优点:解耦,在现有系列之间切换灵活;缺点:扩展产品类型时很麻烦,从头改到尾
GOF四人帮对代理模式的描述:为其他对象提供一种代理以控制对这个对象的访问,其实就是访问对象时引入一种间接性,因为这种间接性可以附加对种用途。
远程代理, 也就是为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象从在于不同地址空间的事实。比如我们用到的webservice 在项目中生成的webReference文件夹和文件使得客户端程序调用代理就可以解决远程访问的问题。
虚拟代理, 根据需求创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。比如我们打开一个很大的HTML页面时,里面有很多的图片和文字,我们可以很快打开这个网页看到上面的文字,但是图片却是一张张加载才能看到。而那些未打开的图片框是通过虚拟代理来替代真实图片,此时代理存储的是真实的图片路径和尺寸。
安全代理 , 用来控制真实对象访问权限,一般用于对象应该有不同的访问权限的时候。
智能指引 ,通过代理在访问一个对象时增加一些内务处理,比如统计这个对象的引用次数,检查这个对象是否已经锁定以确保其他对象不能改变它等。
Spring中 的AOP实现。下面整理了一些好的参考博文供大家学习。
AOP 学习案例
静态代理
基于接口实现的动态代理和基于继承实现的CGlib代理
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 。
JAVA中的克隆
什么情况下会用到克隆技术?
克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。
注意*:我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。而通过clone方法赋值的对象跟原来的对象时同时独立存在的。*
浅拷贝:
被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
class Student implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
stu2.setNumber(54321);
System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
}
}
深拷贝
class Student implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
stu2.setNumber(54321);
System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
}
}
class Student implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
stu2.setNumber(54321);
System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
}
}
1、浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。
2、深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。
(如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。)
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
详情参考:https://www.cnblogs.com/Qian123/p/5710533.html
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重新定义改算法的某些特定步骤。
AbstractClass是一个抽象的模板类,定义了实现一个模板的方法,这个模板方法一般是具体的方法,它给出了一个顶级的逻辑骨架,而逻辑组成的步骤在相应的抽象操作中,推迟到了子类中实现。顶级逻辑也有可能调用一些具体的方法。
当不变的行为方法和变化的行为方法混合在子类中重复出现的时候,我们通常模板方法模式通常是把不变的行为搬到了超类中去除子类重复代码。有没有很熟悉,没错就是我们不断向上抽取的过程
下面来看个简单的栗子。
假设提供一种造房子的算法。算法的步骤就是模拟造房子的过程:建地基、建支撑,最后添加墙和窗户 – 1. Fundation,2. Pillars,3. Walls,4. Windows。最重要的一点就是不能改变此建造过程,比如不可能在没用地基的时候就开始建造窗户。这个例子中,我们就创建了一个模板方法 – 将使用不同的方法完成对房子的建造。
为了确保子类不能重写(override)这个模板方法,应当使用final。
模板抽象类:
10 // template method, final so subclasses can't override
11 public final void buildHouse() {
12 buildFoundation();
13 buildPillars();
14 buildWalls();
15 buildWindows();
16 System.out.println("House is built.");
17 }
18
19 // default implementation
20 private void buildWindows() {
21 System.out.println("Building Glass Windows");
22 }
23
24 // methods to be implemented by subclasses
25 public abstract void buildWalls();
26
27 public abstract void buildPillars();
28
29 private void buildFoundation() {
30 System.out.println("Building foundation with cement,iron rods and sand");
31 }
32
33 }
具体实现子类1:
11
12
18 @Override
19 public void buildWalls() {
20 System.out.println("Building Glass Walls");
21 }
22
2
29 @Override
30 public void buildPillars() {
31 System.out.println("Building Pillars with glass coating");
32
33 }
34
35 }
具体的实现子类2:
11
18 @Override
19 public void buildWalls() {
20 System.out.println("Building Glass Walls");
21 }
22
23
29 @Override
30 public void buildPillars() {
31 System.out.println("Building Pillars with glass coating");
32
33 }
34
35 }
测试类:
public class GlassHouse extends HouseTemplate{
11
18 @Override
19 public void buildWalls() {
20 System.out.println("Building Glass Walls");
21 }
29 @Override
30 public void buildPillars() {
31 System.out.println("Building Pillars with glass coating");
32
33 }
34
35 }
JDK中模板方法模式的使用
java.io.InputStream, java.io.OutputStream, java.io.Reader 以及 java.io.Writer 中所有非抽象方法。
java.util.AbstractList, java.util.AbstractSet 以及 java.util.AbstractMap中所有非抽象方法。
为子系统中的一组接口提供一致的界面,此模式定义了一个高级接口,这个接口使得这一子系统更加容易使用。
个人理解:在实际开发中由于主系统的业务逻辑比较复杂,其他的子系统想要对接主系统,此时我们会整理一些业务提供给其他系统调用而其他系统不必关心我们主系统业务的具体实现,这里的接口抽取设计成简单清晰的的接口就可以理解为外观Facade类接口。
将一个复杂的对象构建与他的表示分离,使得同样的构建过程可以创建不同的表示。指挥者(Director)直接和客户(Client)进行需求沟通;
沟通后指挥者将客户创建产品的需求划分为各个部件的建造请求(Builder);
将各个部件的建造请求委派到具体的建造者(ConcreteBuilder);
各个具体建造者负责进行产品部件的构建;
最终构建成具体产品(Product)。
eg:用 builder 模式创建共享单车为例子
产品类:
private IFrame frame;
private ISeat seat;
private ITire tire;
public IFrame getFrame() {
return frame;
}
public void setFrame(IFrame frame) {
this.frame = frame;
}
public ISeat getSeat() {
return seat;
}
public void setSeat(ISeat seat) {
this.seat = seat;
}
public ITire getTire() {
return tire;
}
public void setTire(ITire tire) {
this.tire = tire;
}
}
抽象建造者类:
abstract void buildFrame();
abstract void buildSeat();
abstract void buildTire();
abstract Bike createBike();
}
具体的建造者类
private Bike mBike = new Bike();
@Override
void buildFrame() {
mBike.setFrame(new AlloyFrame());
}
@Override
void buildSeat() {
mBike.setSeat(new DermisSeat());
}
@Override
void buildTire() {
mBike.setTire(new SolidTire());
}
@Override
Bike createBike() {
return mBike;
}
}
public class OfoBuilder extends Builder{
private Bike mBike = new Bike();
@Override
void buildFrame() {
mBike.setFrame(new CarbonFrame());
}
@Override
void buildSeat() {
mBike.setSeat(new RubberSeat());
}
@Override
void buildTire() {
mBike.setTire(new InflateTire());
}
@Override
Bike createBike() {
return mBike;
}
}
指挥者类:
private Builder mBuilder = null;
public Director(Builder builder) {
mBuilder = builder;
}
public Bike construct() {
mBuilder.buildFrame();
mBuilder.buildSeat();
mBuilder.buildTire();
return mBuilder.createBike();
}
}
客户端调用:
public class Click {
public static void main(String[] args) {
showBike(new OfoBuilder());
showBike(new MobikeBuilder());
}
private void showBike(Builder builder) {
Director director = new Director(builder);
Bike bike = director.construct();
bike.getFrame().frame();
bike.getSeat().seat();
bike.getTire().tire();
}
}
建造者模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及他们的装配方式的试用的模式。
优点:
缺点:
与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族 。
在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象 。
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时会通知所有观察者对象更新自己。
没错大家熟悉的MQ 像RabbitMQ ,ActiveMQ 等这些消息中间件中有一种消息传输模式就是这种发布订阅模式实现思路。
观察者所做的工作其实就是解除耦合,让耦合的双方都依赖抽象,而不是依赖具体。从而使得各自的变化都不会影响另一方的变化。
下面来看一个简单的实现例子
首先是一个Subject类的父类,它实现了维护装有观察者引用集合的功能。
//保存注册的观察者对象
private List mObervers = new ArrayList<>();
//注册观察者对象
public void attach(Observer observer) {
mObervers.add(observer);
}
//注销观察者对象
public void detach(Observer observer) {
mObervers.remove(observer);
}
//通知所有注册的观察者对象
public void notifyEveryOne(String newState) {
for (Observer observer : mObervers) {
observer.update(newState);
}
}
}
接着是一个具体的被观察对象
private String state;
public String getState() {
return state;
}
public void change(String newState) {
state = newState
//状态发生改变,通知观察者
notifyEveryOne(newState);
}
}
然后是观察者Observer对象 抽象出一个及时更新的方法
void update(String newState);
}
具体的观察者类:
//观察者状态
private String observerState;
@Override
public void update(String newState) {
//更新观察者状态,让它与目标状态一致
observerState = newState;
}
}
public class ObserverB implements Observer {
//观察者状态
private String observerState;
@Override
public void update(String newState) {
//更新观察者状态,让它与目标状态一致
observerState = newState;
}
}
public class ObserverC implements Observer {
//观察者状态
private String observerState;
@Override
public void update(String newState) {
//更新观察者状态,让它与目标状态一致
observerState = newState;
}
}
测试类:
public static void main(String[] args) {
//创建主题(被观察者)
ConcreteSubject magazine = new ConcreteSubject ();
//创建三个不同的观察者
ObserverA a = new ObserverA ("A");
ObserverB b = new ObserverB ("B");
ObserverC c = new ObserverC ("C");
//将观察者注册到主题中
magazine.attach(a);
magazine.attach(b);
magazine.attach(c);
//更新主题的数据,当数据更新后,会自动通知所有已注册的观察者
String State="change";
magazine. change(String change)
//打印观察者a,b,c的状态变化
。。。。。
}
}
大体实现思路几部分代码是这样的。但是观察者模式还有不足之处。
上面的例子中所有的观察者动作都是一样的,但是存在收到状态变更通知后观察者做出的动作是各不相同的那就不能同时依赖一个抽象更新的方法了。
事件委托
委托就是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。委托可以看作是对函数的抽象,是函数的”类“,委托的实例将代表一个具体的函数。一个委托可以搭载多个方法,所有方法被依次唤起。更重要的是,它可以使得委托对象所搭载的方法并不需要属于同一个类。
观察者模式改进-反射版事件委托
反射能够实现根据类的不同而调用不同的方法。就好像是C#中,一个委托可以搭载多个方法,所有方法一次被唤起。具体实现可以参考这个例子
当一个状态的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
使用场景:
状态模式主要 解决的是当控制一个对象的状态转移的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。说白了就是为了消除庞大的条件分支语句。
看看这个例子
与策略模式的比较:
最根本的差异在于策略模式是在求解同一个问题的多种解法,这些不同解法之间毫无关联;状态模式则不同,状态模式要求各个状态之间有所关联,以便实现状态转移。
将一个类的接口转换成客户希望的另一个接口。Adpater模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。
角色
Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。缺省适配器模式是适配器模式的一种变体,其应用也较为广泛。在JDK类库的事件处理包java.awt.event中广泛使用了缺省适配器模式,如WindowAdapter、KeyAdapter、MouseAdapter等。
eg:类适配器
public void adapteeRequest() {
System.out.println("被适配者的方法");
}
}
目标接口
void request();
}
怎么才可以在目标接口中的 request() 调用 Adaptee 的 adapteeRequest() 方法呢?
如果直接实现 Target 是不行的
@Override
public void request() {
System.out.println("concreteTarget目标方法");
}
}
如果通过一个适配器类,实现 Target 接口,同时继承了 Adaptee 类,然后在实现的 request() 方法中调用父类的 adapteeRequest() 即可实现
@Override
public void request() {
//...一些操作...
super.adapteeRequest();
//...一些操作...
}
}
测试类:
public static void main(String[] args) {
Target target = new ConcreteTarget();
target.request();
Target adapterTarget = new Adapter();
adapterTarget.request();
}
}
输出:
被适配者的方法
这样我们即可在新接口 Target 中适配旧的接口或类
对象适配器
对象适配器与类适配器不同之处在于,类适配器通过继承来完成适配,对象适配器则是通过关联来完成,这里稍微修改一下 Adapter 类即可将转变为对象适配器
// 适配者是对象适配器的一个属性
private Adaptee adaptee = new Adaptee();
@Override
public void request() {
//...
adaptee.adapteeRequest();
//...
}
}
适配器模式总结
主要优点:
将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
具体来说,类适配器模式还有如下优点:
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
对象适配器模式还有如下优点:
一个对象适配器可以把多个不同的适配者适配到同一个目标;
可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。
类适配器模式的缺点如下:
对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
对象适配器模式的缺点如下:
与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
适用场景:
系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
JAVA I/O 中InputStream Reader 和OutputeSream Writer 分别继承了Reader和Writer接口但是要创建他们的对象必须在构造函数中传入InputStream 和OutPuteStram 的实例所以中InputStream Reader 和OutputeSream Writer的作用就是将InputStream 和OutPuteStram适配到Reader 和Writer。
推荐阅读:
源码分析适配器模式的典型应用 、 spring AOP中的适配器模式 , spring JPA中的适配器模式,spring MVC中的适配器模式
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象的外部保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
浏览器回退:浏览器一般有浏览记录,当我们在一个网页上点击几次链接之后,可在左上角点击左箭头回退到上一次的页面,然后也可以点击右箭头重新回到当前页面
数据库备份与还原:一般的数据库都支持备份与还原操作,备份即将当前已有的数据或者记录保留,还原即将已经保留的数据恢复到对应的表中
编辑器撤销与重做:在编辑器上编辑文字,写错时可以按快捷键 Ctrl + z 撤销,撤销后可以按 Ctrl + y 重做
虚拟机生成快照与恢复:虚拟机可以生成一个快照,当虚拟机发生错误时可以恢复到快照的样子
Git版本管理:Git是最常见的版本管理软件,每提交一个新版本,实际上Git就会把它们自动串成一条时间线,每个版本都有一个版本号,使用 git reset --hard 版本号 即可回到指定的版本,让代码时空穿梭回到过去某个历史时刻
棋牌游戏悔棋:在棋牌游戏中,有时下快了可以悔棋,回退到上一步重新下
角色
Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。
Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。
备忘录模式的核心是备忘录类以及用于管理备忘录的负责人类的设计。
示例
下棋例子,可以下棋,悔棋,撤销悔棋等
棋子类 Chessman,原发器角色
@AllArgsConstructor
class Chessman {
private String label;
private int x;
private int y;
//保存状态
public ChessmanMemento save() {
return new ChessmanMemento(this.label, this.x, this.y);
}
//恢复状态
public void restore(ChessmanMemento memento) {
this.label = memento.getLabel();
this.x = memento.getX();
this.y = memento.getY();
}
public void show() {
System.out.println(String.format("棋子<%s>:当前位置为:<%d, %d>", this.getLabel(), this.getX(), this.getY()));
}
}
备忘录角色 ChessmanMemento
@AllArgsConstructor
class ChessmanMemento {
private String label;
private int x;
private int y;
}
负责人角色 MementoCaretaker
//定义一个集合来存储备忘录
private ArrayList mementolist = new ArrayList();
public ChessmanMemento getMemento(int i) {
return (ChessmanMemento) mementolist.get(i);
}
public void addMemento(ChessmanMemento memento) {
mementolist.add(memento);
}
}
棋子客户端,维护了一个 MementoCaretaker 对象
private static int index = -1;
private static MementoCaretaker mc = new MementoCaretaker();
public static void main(String args[]) {
Chessman chess = new Chessman("车", 1, 1);
play(chess);
chess.setY(4);
play(chess);
chess.setX(5);
play(chess);
undo(chess, index);
undo(chess, index);
redo(chess, index);
redo(chess, index);
}
//下棋,同时保存备忘录
public static void play(Chessman chess) {
mc.addMemento(chess.save());
index++;
chess.show();
}
//悔棋,撤销到上一个备忘录
public static void undo(Chessman chess, int i) {
System.out.println("******悔棋******");
index--;
chess.restore(mc.getMemento(i - 1));
chess.show();
}
//撤销悔棋,恢复到下一个备忘录
public static void redo(Chessman chess, int i) {
System.out.println("******撤销悔棋******");
index++;
chess.restore(mc.getMemento(i + 1));
chess.show();
}
}
输出如下,悔棋成功,撤销悔棋成功
备忘录模式总结
备忘录模式的主要优点如下:
它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。
备忘录模式的主要缺点如下:
资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。
适用场景:
保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时它能够恢复到先前的状态,实现撤销操作。
防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
eg:。把学校作为根节点,学院做普通节点,专业就是叶子。
首先,从上面的类图中知道,对于客户端来说,其需要面对的就是一个组件,而不用管是否节点还是叶子。我们就先来创建这样一个组件
/**
* description:
* 机构组件,学院和系称为机构
*/
public abstract class OrganizationComponent {
// 每个方法提供默认的实现
public void add(OrganizationComponent organizationComponent){
throw new UnsupportedOperationException();
}
public void remove(OrganizationComponent organizationComponent){
throw new UnsupportedOperationException();
}
public String getName(){
throw new UnsupportedOperationException();
}
public String getDescription(){
throw new UnsupportedOperationException();
}
public void print(){
throw new UnsupportedOperationException();
}
}
创建大学对象:
import com.jet.pattern.Composite.OrganizationComponent;
import java.util.ArrayList;
import java.util.List;
/**
* description:
* 大学对象
*/
public class University extends OrganizationComponent {
String name;
String description;
List organizationComponents = new ArrayList();
public University(String name, String description) {
this.name = name;
this.description = description;
}
// 重写机构组件的方法,其作为树有增加和删除方法
@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public void print() {
System.out.println("-------" + getName() + "-----------");
// 大学下面有很多学院,把他们遍历出来
for(OrganizationComponent organizationComponent : organizationComponents){
organizationComponent.print();
}
}
}
创建学院对象:
import com.jet.pattern.Composite.OrganizationComponent;
import java.util.ArrayList;
import java.util.List;
/**
* description:
* 学院是一个机构,
*/
public class College extends OrganizationComponent {
String name;
String description;
List organizationComponents = new ArrayList();
public College(String name, String description) {
this.name = name;
this.description = description;
}
// 重写机构组件的方法,其作为树有增加和删除方法
@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public void print() {
System.out.println("-------" + getName() + "-----------");
// 学院下面有很多专业,把他们遍历出来
for(OrganizationComponent organizationComponent : organizationComponents){
organizationComponent.print();
}
}
}
创建专业(系)对象:
import com.jet.pattern.Composite.OrganizationComponent;
import java.util.ArrayList;
import java.util.List;
/**
* description:
* 学院是一个机构,
*/
public class College extends OrganizationComponent {
String name;
String description;
List organizationComponents = new ArrayList();
public College(String name, String description) {
this.name = name;
this.description = description;
}
// 重写机构组件的方法,其作为树有增加和删除方法
@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public void print() {
System.out.println("-------" + getName() + "-----------");
// 学院下面有很多专业,把他们遍历出来
for(OrganizationComponent organizationComponent : organizationComponents){
organizationComponent.print();
}
}
}
模拟客户端创建对象:
import com.jet.pattern.Composite.OrganizationComponent;
/**
* description:
* 输出信息,模拟客户调用
*/
public class OutputInfo {
OrganizationComponent allOrganization;
public OutputInfo(OrganizationComponent allOrganization) {
this.allOrganization = allOrganization;
}
public void printOrganization(){
allOrganization.print();
}
}
测试类:
import com.jet.pattern.Composite.OrganizationComponent;
import com.jet.pattern.Composite.impl.College;
import com.jet.pattern.Composite.impl.Department;
import com.jet.pattern.Composite.impl.OutputInfo;
import com.jet.pattern.Composite.impl.University;
/**
* description:
* 测试组合模式
*/
public class CompositeTest {
public static void main(String[] args) {
// 从大到小创建对象,学院和专业组合成为学校,先创建学校,它也是机构组件
OrganizationComponent university = new University("清华大学","全国最好的大学");
// 接着创建学院
OrganizationComponent computerCollege = new College("计算机学院","计算机学院");
OrganizationComponent infoEngineeringCollege = new College("信息工程学院","信息工程学院");
// 计算机学院有下面专业
computerCollege.add(new Department("计算机科学与技术","计算机科学与技术"));
computerCollege.add(new Department("软件工程 ","软件工程"));
computerCollege.add(new Department("网络工程","网络工程"));
// 信息工程学院有下面专业
infoEngineeringCollege.add(new Department("通信工程","通信工程"));
infoEngineeringCollege.add(new Department("信息工程","信息工程"));
// 学校有下面学院
university.add(computerCollege);
university.add(infoEngineeringCollege);
// 输出学校机构信息
OutputInfo info = new OutputInfo(university);
info.printOrganization();
}
}
组合模式的优缺点
优点:
组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。
将”客户代码与复杂的对象容器结构“解耦。
可以更容易地往组合对象中加入新的构件。
缺点:
使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。(这个是几乎所有设计模式所面临的问题)。
注意的问题:
有时候系统需要遍历一个树枝结构的子构件很多次,这时候可以考虑把遍历子构件的结构存储在父构件里面作为缓存。
客户端尽量不要直接调用树叶类中的方法(在我上面实现就是这样的,创建的是一个树枝的具体对象;),而是借用其父类(Graphics)的多态性完成调用,这样可以增加代码的复用性。
组合模式的使用场景:
当想表达对象的部分-整体的层次结构时。
希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象时。
提供一种方法顺序访问一个聚合对象中各元素,而又不暴露该对象的内部表示。
详情见我的另一篇文章 https://mp.csdn.net/mdeditor/94433906#
将抽象部分与他的实现部分分离,使他们都可以独立的变化。
抽象与实现分离并不是说让抽象类与派生类分离,这没有意义。实现指的是让抽象类和他的派生类用来实现自己的对象。比如让手机既可以按照品牌来分类,也可以按照功能来分类。从多种角度分离出来让他们独立变化,减少他们之间的耦合。
示例代码如下:
首先定义Implementor接口,其中定义了其实现类必须要实现的接口operation()
1 public interface Implementor {
2 public void operation();
3 }
下面定义Implementor接口的两个实现类:
1 public class ConcreateImplementorA implements Implementor {
2 @Override
3 public void operation() {
4 System.out.println("this is concreteImplementorA's operation...");
5 }
6 }
public class ConcreateImplementorB implements Implementor {
2 @Override
3 public void operation() {
4 System.out.println("this is concreteImplementorB's operation...");
5 }
6 }
下面定义桥接类Abstraction,其中有对Implementor接口的引用:
public abstract class Abstraction {
private Implementor implementor;
public Implementor getImplementor() {
return implementor;
}
public void setImplementor(Implementor implementor) {
this.implementor = implementor;
}
protected void operation(){
implementor.operation();
}
}
下面是Abstraction类的子类RefinedAbstraction:
1 public class RefinedAbstraction extends Abstraction {
2 @Override
3 protected void operation() {
4 super.getImplementor().operation();
5 }
6 }
public class BridgeTest {
public static void main(String[] args) {
Abstraction abstraction = new RefinedAbstraction();
//调用第一个实现类
abstraction.setImplementor(new ConcreateImplementorA());
abstraction.operation();
//调用第二个实现类
abstraction.setImplementor(new ConcreateImplementorB());
abstraction.operation();
}
}
这样,通过对Abstraction桥接类的调用,实现了对接口Implementor的实现类ConcreteImplementorA和ConcreteImplementorB的调用。实现了抽象与行为实现的分离。
总结:
1.桥接模式的优点
(1)实现了抽象和实现部分的分离
桥接模式分离了抽象部分和实现部分,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,分别定义接口,这有助于系统进行分层设计,从而产生更好的结构化系统。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了。
(2)更好的可扩展性
由于桥接模式把抽象部分和实现部分分离了,从而分别定义接口,这就使得抽象部分和实现部分可以分别独立扩展,而不会相互影响,大大的提供了系统的可扩展性。
(3)可动态的切换实现
由于桥接模式实现了抽象和实现的分离,所以在实现桥接模式时,就可以实现动态的选择和使用具体的实现。
(4)实现细节对客户端透明,可以对用户隐藏实现细节。
2.桥接模式的缺点
(1)桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。
3.桥接模式的使用场景
(1)如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
(2)抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
(3)一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
(4)虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
(5)对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志以及支持可撤销的操作。
创建Command接口
public interface ICommand {
void execute();
}
创建命令执行对象
public class Receiver {
public void execute(){
System.out.println("receiver execute ... ");
}
}
创建具体Command实现类
public class ConcreteCommandA implements ICommand {
private Receiver receiver;
public ConcreteCommandA(Receiver receiver){
this.receiver = receiver;
}
@Override
public void execute() {
System.out.println("ConcreteCommandA execute ...");
receiver.execute();
}
}
public class ConcreteCommandB implements ICommand {
private Receiver receiver;
public ConcreteCommandB(Receiver receiver){
this.receiver = receiver;
}
@Override
public void execute() {
System.out.println("ConcreteCommandB execute ...");
receiver.execute();
}
}
创建命令入口
public class Invoker {
private ICommand concreteCommandA, concreteCommandB;
public Invoker(ICommand concreteCommandA, ICommand concreteCommandB){
this.concreteCommandA = concreteCommandA;
this.concreteCommandB = concreteCommandB;
}
public void orderA(){
concreteCommandA.execute();
}
public void orderB(){
concreteCommandB.execute();
}
}
调用:
public static void main(String[] args) {
Receiver receiver = new Receiver();
Invoker invoker = new Invoker(new ConcreteCommandA(receiver), new ConcreteCommandB(receiver));
invoker.orderA();
invoker.orderB();
}
输出:
简单比较命令模式和策略模式
命令模式是含有不同的命令(含有接收者的请求):做不同的事情;隐藏接收者执行细节。常见菜单事件,而策略模式含有不同的算法,做相同的事情;
区别在于是否含有接收者。命令模式含有,策略模式不含有。
命令模式中的命令可以单独运行
打个比喻就是:
命令模式等于菜单中的复制,移动,压缩等,而策略模式是其中一个菜单的例如复制到不同算法实现。
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递请求,直到有一个对象处理他为止。
角色:
抽象处理者(Handler)角色:该角色对请求进行抽象,并定义一个方法来设定和返回对下一个处理者的引用。
具体处理者(Concrete Handler):该角色接到请求后,可以选择将请求处理掉,或者将请求传给下一个处理者。由于具体处理者持有对下一个处理者的引用,因此,如果需要,处理者可以访问下一个处理者。
* 抽象处理器
*/
public abstract class Handler {
//下一个处理器
private Handler nextHandler;
//处理方法
public abstract void handleRequest();
public Handler getNextHandler() {
return nextHandler;
}
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
}
/**
* 具体处理器.
*/
public class ConcreteHandler extends Handler {
@Override
public void handleRequest() {
System.out.println(this.toString()+"处理器处理");
if (getNextHandler()!=null){ //判断是否存在下一个处理器
getNextHandler().handleRequest(); //存在则调用下一个处理器
}
}
}
/**
* 测试
*/
public class Client {
public static void main(String[] args) {
Handler h1 = new ConcreteHandler();
Handler h2 = new ConcreteHandler();
h1.setNextHandler(h2); //h1的下一个处理器是h2
h1.handleRequest();
}
}
优点
责任链模式将请求和处理分开,请求者不知道是谁处理的,处理者可以不用知道请求的全貌。
提高系统的灵活性。
缺点
降低程序的性能。每个请求都是从链头遍历到链尾,当链比较长的时候,性能会大幅下降。
不易于调试。由于该模式采用了类似递归的方式,调试的时候逻辑比较复杂。
应用场景
责任链模式是一种常见的模式,Struts2的核心控件FilterDispatcher是一个Servlet过滤器,该控件就是采用责任链模式,可以对用户请求进行层层过滤处理。责任链模式在实际项目中的使用比较多,其典型的应用场景如下
* 抽象处理器.
*/
public abstract class AbstractLogger {
public static final int INFO = 1; //一级日志
public static final int DEBUG = 2; //二级日志包括一级
public static final int ERROR = 3; //三级包括前两个
protected int level;
//责任链下一个元素
protected AbstractLogger nextLogger ;
public void setNextLogger(AbstractLogger nextLogger){
this.nextLogger = nextLogger;
}
//不同级别的记录方法不一样,这里给一个抽象的记录方法
abstract protected void write(String message);
//调用责任链处理器的记录方法.并且判断下一个责任链元素是否存在,若存在,则执行下一个方法.
public void logMessage(int level,String message){
if (this.level <= level){ //根据传进来的日志等级,判断哪些责任链元素要去记录
write(message);
}
if (nextLogger != null){
nextLogger.logMessage(level,message); //进行下一个责任链元素处理
}
}
}
/**
* 控制台处理器.
*/
public class ConsoleLogger extends AbstractLogger {
public ConsoleLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Standard Console::Logger :"+message);
}
}
/**
* 文件处理器.
*/
public class FileLogger extends AbstractLogger {
public FileLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("File Console::Logger"+message);
}
}
/**
* error日志处理器.
*/
public class ErrorLogger extends AbstractLogger {
public ErrorLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Error Console::Logger: " + message);
}
}
/**
* 处理链.
*/
public class ChainPatternDemo {
public static AbstractLogger getChainOfLoggers() {
AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);
return errorLogger;
}
}
public class Main {
public static void main(String[] args) {
AbstractLogger logger = ChainPatternDemo.getChainOfLoggers();
logger.logMessage(1,"一级日志记录");
System.out.println("--------------------------------");
logger.logMessage(2,"二级日志记录");
System.out.println("--------------------------------");
logger.logMessage(3,"三级日志记录");
}
}
用一个中介对象来封装一系列的对象交互。中介者使各对象之间不需要显示地相互引用,从而使其耦合松散,而且可以独立的改变他们之间的交互。
首先是抽象中介者:Mediator.java
//申明一个联络方法
public abstract void constact(String message,Person person);
}
抽象同事类:Person.java
protected String name;
protected Mediator mediator;
Person(String name,Mediator mediator){
this.name = name;
this.mediator = mediator;
}
}
两个具体同事类:HouseOwner.java
HouseOwner(String name, Mediator mediator) {
super(name, mediator);
}
/**
* @desc 与中介者联系
* @param message
* @return void
*/
public void constact(String message){
mediator.constact(message, this);
}
/**
* @desc 获取信息
* @param message
* @return void
*/
public void getMessage(String message){
System.out.println("房主:" + name +",获得信息:" + message);
}
}
中介结构、MediatorStructure.java
//首先中介结构必须知道所有房主和租房者的信息
private HouseOwner houseOwner;
private Tenant tenant;
public HouseOwner getHouseOwner() {
return houseOwner;
}
public void setHouseOwner(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
public void constact(String message, Person person) {
if(person == houseOwner){ //如果是房主,则租房者获得信息
tenant.getMessage(message);
}
else{ //反正则是房主获得信息
houseOwner.getMessage(message);
}
}
}
Client.java
public static void main(String[] args) {
//一个房主、一个租房者、一个中介机构
MediatorStructure mediator = new MediatorStructure();
//房主和租房者只需要知道中介机构即可
HouseOwner houseOwner = new HouseOwner("张三", mediator);
Tenant tenant = new Tenant("李四", mediator);
//中介结构要知道房主和租房者
mediator.setHouseOwner(houseOwner);
mediator.setTenant(tenant);
tenant.constact("听说你那里有三室的房主出租.....");
houseOwner.constact("是的!请问你需要租吗?");
}
}
优点
1、 简化了对象之间的关系,将系统的各个对象之间的相互关系进行封装,将各个同事类解耦,使系统成为松耦合系统。
2、 减少了子类的生成。
3、 可以减少各同事类的设计与实现。
缺点
由于中介者对象封装了系统中对象之间的相互关系,导致其变得非常复杂,使得系统维护比较困难。
場景:
中介者模式一般用于已定义良好但是以复杂的方式进行通信的场合。以及想定制一个分布在多个类中的行为,而又不想生成太多子类的场合。
总结:
1、 在中介者模式中通过引用中介者对象,将系统中有关的对象所引用的其他对象数目减少到最少。它简化了系统的结构,将系统由负责的网状结构转变成简单的星形结构,中介者对象在这里起到中转和协调作用。
2、 中介者类是中介者模式的核心,它对整个系统进行控制和协调,简化了对象之间的交互,还可以对对象间的交互进行进一步的控制。
3、 通过使用中介者模式,具体的同事类可以独立变化,通过引用中介者可以简化同事类的设计和实现。
4、 就是由于中介者对象需要知道所有的具体同事类,封装具体同事类之间相互关系,导致中介者对象变得非常复杂,系统维护起来较为困难。
运用共享技术有效的支持大量细粒度的对象。
了解更多:https://www.runoob.com/design-pattern/flyweight-pattern.html
给定一个语言,定义他的一种文法的表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
匹配字符串时我们所用到的正则表达式就是这种设计模式,还比如浏览器。
了解更多:https://blog.csdn.net/niunai112/article/details/79982712
表示一个作用于某对象结构中的各个元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
了解更多:https://www.cnblogs.com/edisonchou/p/7247990.html