设计模式初识
在开始之前,先看下设计模式的六大原则
-
总原则:开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。
-
单一职责原则
不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。
-
里氏替换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP 是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。 —— From Baidu 百科历史替换原则中,子类对父的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
-
依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
-
接口隔离原则(Interface Segregation Principle)
这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
-
迪米特法则(最少知道原则)(Demeter Principle)
就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过 public 方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要为局部变量出现在类中。
-
合成复用原则(Composite Reuse Principle)
原则是尽量首先使用合成/聚合的方式,而不是使用继承。
创建型模式(五种)
- 工厂方法模式
- 抽象工厂模式
- 单例模式
- 建造者模式
- 原型模式
结构型模式(七种)
- 适配器模式
- 装饰器模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式
行为型模式(十一种)
- 策略模式
- 模板方法模式
- 观察者模式
- 迭代子模式
- 责任链模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
其实还有两类:并发型模式和线程池模式
在这之前,先了解下不在23种设计模式之中的简单工厂模式
简单工厂模式(不在23种设计模式之列)
先定义一个接口
public interface Sender {
public void send();
}
再写两个实现类
public class MailSender implements Sender {
@Override
public void send() {
System.out.println("this is a mailSender");
}
}
public class SMSSender implements Sender {
@Override
public void send() {
System.out.println("this is a SMSSender");
}
}
最基本的工厂方式是根据名称来进行对象的获取
public class SenderFactory {
public Sender producer(String sendFactoryName){
if("mail".equals(sendFactoryName)){
return new MailSender();
}else if ("sms".equals(sendFactoryName)){
return new SMSSender();
}else{
return null;
}
}
}
但是这样根据名称来进行获取的话 看上去有点很low,可以写成下面这样的(根据生产者来直接拿对象)
public class SenderFactoryTWO {
/**
* get mail sender
* @return
*/
public Sender mailProducer(){
return new MailSender();
}
/**
* get sms sender
* @return
*/
public Sender smsProducer(){
return new SMSSender();
}
}
但是这样每次使用的时候还需要进行基类的创建,所以可以改成下面这样的
public class SenderFactoryThree {
public static Sender mailProducer(){
return new MailSender();
}
public static Sender smsProducer(){
return new SMSSender();
}
}
这种看上去是最方便的,也是简单工厂模式最普遍的使用模式
但是针对上面的代码我们可以看出,简单工厂模式针对大量的对象需要根据不同情况手动的去进行创建,并且需要实现共同的接口,但是如果想要拓展程序,那么必须对工厂类进行修改,那这就违反了闭包原则(对基类去进行修改),这样的话可以引入下面的工厂模式来进行考虑,有着一个工厂接口,针对不同情况去增加新的工厂实现类,这样的话就避免了对以前代码的修改,每个工厂都有着对应的产品
创建型模式
工厂模式
同样的先去定义两个接口
这个是发送者接口
public interface Sender {
public void send();
}
这个是提供者(工厂提供者)
public interface Provider {
public Sender producer();
}
这个是具体发送者和提供工厂一
public class MailSender implements Sender {
@Override
public void send() {
System.out.println("this is a mailSender");
}
}
public class MailSenderFactory implements Provider {
@Override
public Sender producer() {
return new MailSender();
}
}
这个是具体发送者和提供工厂二
public class SMSSender implements Sender {
@Override
public void send() {
System.out.println("this is a SMSSender");
}
}
public class SmsSenderFactory implements Provider {
@Override
public Sender producer() {
return new SMSSender();
}
}
这样的话 我们在使用的时候,不用对生产者进行修改
public class FactoryMethodTest {
public static void main(String[] args) {
mailTest();
}
public static void mailTest(){
Provider provider = MailSender::new;
provider.producer().send();
}
}
如果有新的生产者过来的话,我们可以创建一个新的生产者和生产工厂去实现Sender接口和Provider接口就可以了,但是这样的话,每个工厂类只能创建一个具体的产品类的实例,所以接下来我们可以看看能生产一系列的产品类的抽象工厂模式
抽象工厂模式
假设一台电脑的基本构成只有cpu,键盘,屏幕和鼠标,这个绝对是假设啊,那么我们可以去定义四个接口
public interface Cpu {
void create();
}
public interface KeyBoard {
void create();
}
public interface Mouse {
void create();
}
public interface Screen {
void create();
}
定义四个具体的类去实现这四个接口
public class NormalCpu implements Cpu {
@Override
public void create() {
System.out.println("cpu create");
}
}
public class NormalKeyBoard implements KeyBoard {
@Override
public void create() {
System.out.println("keyBoard create");
}
}
public class NormalMouse implements Mouse {
@Override
public void create() {
System.out.println("mouse create");
}
}
public class NormalScreen implements Screen {
@Override
public void create() {
System.out.println("screen create");
}
}
再去定义一个抽象类,在里面定义抽象的方法
public abstract class AbstractComputerFactory {
public abstract Cpu createCpu();
public abstract KeyBoard createKeyBoard();
public abstract Mouse createMouse();
public abstract Screen createScreen();
}
定义具体的实现类
public class AbstractFactoryImpl extends AbstractComputerFactory {
@Override
public Cpu createCpu() {
return new NormalCpu();
}
@Override
public KeyBoard createKeyBoard() {
return new NormalKeyBoard();
}
@Override
public Mouse createMouse() {
return new NormalMouse();
}
@Override
public Screen createScreen() {
return new NormalScreen();
}
}
再看测试类
public class AbstractFactoryTest {
public static void main(String[] args) {
AbstractComputerFactory abstractComputerFactory = new AbstractFactoryImpl();
abstractComputerFactory.createCpu().create();
abstractComputerFactory.createKeyBoard().create();
abstractComputerFactory.createMouse().create();
abstractComputerFactory.createScreen().create();
}
}
工厂方法创建 “一种” 产品,他的着重点在于"怎么创建",也就是说如果你开发,你的大量代码很可能围绕着这种产品的构造,初始化这些细节上面。也因为如此,类似的产品之间有很多可以复用的特征,所以会和模版方法相随。抽象工厂的话需要创建一些列产品,着重点在于"创建哪些"产品上,也就是说,如果你开发,你的主要任务是划分不同差异的产品线,并且尽量保持每条产品线接口一致,从而可以从同一个抽象工厂继承。
工厂方法和抽象工厂最大的区别
-
工厂方法:一个非常专非常专的专卖店,ta只能生产一类商品中的一种的商品
-
抽象工厂:一个非常专的专卖店,ta可以生产各类商品构成一个商品簇,但是每一类商品ta也是只能生产一种
建造者模式
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的 这个模式将很多功能集成到一个类里,这个类可以创造出比较复杂的东西。所以与工程模式的区别就是:工厂模式关注的是创建单个产品,而建造者模式则关注创建符合对象,多个部分。因此,是选择工厂模式还是建造者模式,依实际情况而定
建造者模式的角色定义,在建造者模式中存在以下4个角色:
- builder:为创建一个产品对象的各个部件指定抽象接口。
- ConcreteBuilder:实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,并提供一个检索产品的接口。
- Director:构造一个使用Builder接口的对象。
- Product:表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程,包含定义组成部件的类,包括将这些部件装配成最终产品的接口。
接下来贴出UML图
[外链图片转存失败(img-gI57j7rK-1567065365650)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\1566818098334.png)]
从上图可以看出Builder是为创建一个Product对象的各个部件指定的抽象接口,Prodcut是具体的产品。Director是一个建造者,由它指挥使用BUilder。ConcreteBuilder是具体的建造,实现Builder接口的各个方法。
下面上具体的java代码
抽象的建造者 这边就是流程的规定
/**
* 抽象建造
*/
public interface Builder {
/**
* 打基础
*/
public void buildBasic();
/**
* 砌墙
*/
public void buildWalls();
/**
* 封顶
*/
public void roofed();
/**
* 造房子
* @return
*/
public Product buildProduct();
}
具体干活的类
/**
* 具体的建造
*/
public class ConcreteBuilder implements Builder{
private Product product;
public ConcreteBuilder(){
product=new Product();
}
@Override
public void buildBasic() {
product.setBasic("打好基础");
}
@Override
public void buildWalls() {
product.setWall("砌墙");
}
@Override
public void roofed() {
product.setRoofed("封顶大吉");
}
@Override
public Product buildProduct() {
return product;
}
}
建造者 这个就是一个类似指挥官的角色
/**
* 建造者
*/
public class Director {
public Product constructProduct(Builder builder){
builder.buildBasic();
builder.buildWalls();
builder.roofed();
return builder.buildProduct();
}
}
具体的产品
/**
* 具体产品
*/
public class Product {
private String basic;//地基
private String wall;//墙
private String roofed;//楼顶
public String getBasic() {
return basic;
}
public void setBasic(String basic) {
this.basic = basic;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoofed() {
return roofed;
}
public void setRoofed(String roofed) {
this.roofed = roofed;
}
@Override
public String toString() {
return "Product{" +
"basic='" + basic + '\'' +
", wall='" + wall + '\'' +
", roofed='" + roofed + '\'' +
'}';
}
}
测试类
public class BuilderTest {
public static void main(String[] args) {
Director director = new Director();
System.out.println(director.constructProduct(new ConcreteBuilder()).toString());
}
}
可能看到现在 你会感觉建造者模式和抽象工厂模式很像,但是要注意的是建造者模式所有函数加到一起才能生成一个对象,就好比这边的产品,所以步骤完成后会发现这个知识一个产品,这个在mybatis里面的SqlSessionFactory的build()的时候就使用了建造者模式,而抽象工厂这边是一个函数就会生成一个对象,具体使用实例可以慢慢看
原型模式
原型模式的话,思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象,在java中,是通过clone来进行实现的
个人感觉在这个原型模式的时候要注意的点就是浅复制和深复制,浅复制的话,就是该类对象实现 Cloneable 接口,调用super.clone()方法,这种情况下,如果复制对象中有引用对象的话,复制出来的对象持有的是引用地址,修改的话会引起两个对象的变化,这样会造成不可预估的错误,那这种情况下我们就会去考虑进行深复制了,这样不论是基本的数据类型还是引用类型,都是重新创建的,采用刘的形式来实现当前对象的二进制输入,在写出二进制数据对应的对象
这边的话就不写太多代码了,贴个复制的基本demo
public class PrototypeMode implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
// 基本数据类型
private String string;
// 这个是引用对象,随意创建 这边考虑的是浅复制和深复制不同的影响
private SerializableObject obj;
public SerializableObject getObj() {
return obj;
}
public void setObj(SerializableObject obj) {
this.obj = obj;
}
/**
* 浅copy 将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的
* @return
* @throws CloneNotSupportedException
*/
public Object shadowClone() throws CloneNotSupportedException{
PrototypeMode clone = (PrototypeMode)super.clone();
return clone;
}
/**
* 深copy 将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底 要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象。
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public Object deepClone() throws IOException, ClassNotFoundException {
/* 写入当前对象的二进制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 读出二进制流产生的新对象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
}
单例模式
单例模式来说的话,大家应该这个是最熟悉的,因为这个是最常见的入门设计模式,我一开始接触的也是这个单例模式
顾名思义的话,所谓的单例模式,就是在程序运行的过程中,能保证在一个 JVM中,该对象只有一个实例存在。这样的模式有几个好处:
- 某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
- 省去了 new 操作符,降低了系统内存的使用频率,减轻 GC 压力。
- 有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
单例基础的入门demo应该是一个懒汉式和一个饿汉式,所谓的懒汉式就是懒加载,而饿汉式的话是在类开始加载的时候就进行初始化了
饿汉式demo
public class HungrySinglton {
/**
* @Decription: 私有化构造方法
*/
private HungrySinglton(){}
// 初始化实例对象 直接返回
private static HungrySinglton instance = new HungrySinglton();
// 获取当前实例对象
public static HungrySinglton getInstance(){
return instance;
}
}
可以看出饿汉式的单例模式因为在类进行加载的时候进行初始化,所以这个是线程安全的
懒汉式demo
/**
* 懒汉式单例模式 这边会有线程安全问题存在
*/
public class LazySingleton {
/**
* 持有私有静态实例,防止被引用,此处赋值为 null,目的是实现延迟加载
*/
private static LazySingleton instance = null;
/**
* 私有化构造方法
*/
private LazySingleton() {
}
/**
* 静态工程方法,创建实例
* @return
*/
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return instance;
}
}
懒汉式单例模式可以看出,如果在并发的情况下,他并不是安全的,因为在getInstance()检查完成之后可能会有别的线程过来检查也是为null,那么创建的对象就不止一个了,那么这种情况下应该使用哪种方式去进行避免,这里面可以考虑用synchronized这个关键字来进行同步,不清楚synchronized这个关键字作用的请去百度进行查询,话不多说,上代码
public class DoubleCheckSinglton {
private DoubleCheckSinglton(){}
// 初始化实例对象 用于返回
private static DoubleCheckSinglton instance ;
// 获取当前实例对象
public static DoubleCheckSinglton getInstance(){
if(null == instance){
synchronized (instance){
if(null == instance){
instance = new DoubleCheckSinglton();
}
}
}
return instance;
}
}
但是这个每次都会去锁该实例对象,那么看上去可能下面这个方法看着会更舒服点
public class Singlton {
private static Singlton singlton;
private Singlton(){
}
/**
* 获取实例
*/
public static Singlton getInstance(){
if(null == singlton){
initSinglton();
}
return singlton;
}
/**
* 初始化实例方法
*/
private synchronized static void initSinglton() {
if(null == singlton){
singlton = new Singlton();
}
}
}
剩下的写个静态内部类来实现的单例模式,这个其实感觉上和懒汉式差不多,都是在类生成的时候创建的,但是不同的是内部类在调用的时候才会进行初始化,这样如果该方法没有去调用的话,很好的避免了资源损耗,至于枚举或者别的写法,有兴趣的可以自行去进行百度,基本上这么多在工作中已经够用了
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
结构型模式
适配器模式
适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类(类的适配器,对象的适配器,接口的适配器),下面简单聊聊这三类的区别
-
类的适配器模式
原理:通过继承来实现适配器功能。当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A,在这种情况下,我们可以定义一个适配器p来进行中转,这个适配器p要实现我们访问的接口A,这样我们就能继续访问当前接口A中的方法(虽然它目前不是我们的菜),然后再继承接口B的实现类BB,这样我们可以在适配器P中访问接口B的方法了,这时我们在适配器P中的接口A方法中直接引用BB中的合适方法,这样就完成了一个简单的类适配器。就是适配器类P实现A接口且继承B的实现类,这样就可以在类P中直接调用A接口的方法来实现B类的方法的调用
这边是类适配器实现的伪代码
Class BImp implements B{
void doSomething(){
// doB()
doSomething();
}
}
Class AImp extends BImp implements A {
void doSomething(){
// 这边用了继承BImp 调用父类的方法就可以了doB()
super.doSomething();
// 这边看看需不需要做别的事情
......
}
}
-
对象的适配器模式
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承类,而是持有类的实例,以达到解决兼容性的问题。
这边也写点伪代码,感觉这个还是蛮好理解的,可能现实中用的也蛮多的但是没有注意到自己使用了这个设计模式
Class BImp implements B{
void doSomething(){
// doB()
doSomething();
}
}
Class AImp implements A {
private B b;
public AImp(B b){
this.b = b;
}
void doSomething(){
// 这边用了传入对象的方法就可以了doB()
b.doSomething();
// 这边看看需不需要做别的事情
......
}
}
这个看上去和类的区别有点,自己去拿实例对象去做些事情
-
接口的适配器模式
有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行
这边的话也写点伪代码,主要是借助抽象类来进行接口的实现,并且在抽象类里面有默认实现,那么后期直接继承该抽象类就可以了,并且针对需要的方法进行重写就行,不需要把接口中所以的未实现方法都去写一遍,但是这个的话是不是违背了不去更改基类方法的原则?这个后期去考虑,先上伪代码
// 接口
public interface Source{
void method1();
void method2();
void method3();
}
// 抽象类
public abstract class AbstractSource implements Source{
@Override
public void method1() {
}
@Override
public void method2() {
}
@Override
public void method3() {
}
}
//具体的需要去使用method1()
public class Target extends AbstractSource{
/**
* 这边只需要用这个方法,就不需要对method2和method3方法进行重写 只需要去重写method1就可以了
*/
@Override
public void method1() {
}
}
这边写下可能使用到的场景吧
- 类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
- 对象的适配器模式: 当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper 类,持有原类的一个实例,在 Wrapper 类的方法中,调用实例的方法就行
- 接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类 Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可
装饰器模式
装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例,这边试着上代码去理解
public interface Sourceable {
public void method();
}
public class SourceableImpl implements Sourceable {
@Override
public void method() {
System.out.println("this is sourceableImpl print ");
}
}
public class Decorator implements Sourceable {
private Sourceable source;
public Decorator(Sourceable source){
super();
this.source = source;
}
@Override
public void method() {
System.out.println("before decorator!");
source.method();
System.out.println("after decorator!");
}
}
看上去很简单,实现共同接口,并根据当前传入的具体对象来对该对象添加一些新的功能,等到后期看到了代理模式可能觉得有点像,但是两者区别还是有的,二者的着重点不同,代理模式偏向于一个控制器,就是把被代理对象的控制权交给了代理对象,由代理对象决定被代理对象是否执行,如何执行装饰模式偏向于一个和花瓶,只能在被装饰对象前后加入一点东西,并不会去阻止执行的
代理模式
这边代理模式的demo看上去会和装饰器模式有点像,具体的可以看下代码
public interface Sourceable {
public void method();
}
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
public class Proxy implements Sourceable {
private Source source;
public Proxy(){
super();
this.source = new Source();
}
@Override
public void method() {
before();
source.method();
atfer();
}
private void atfer() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
}
这个代理模式的代码看上去确实和上面的装饰器模式蛮像的,这边要注意的是装饰者模式使用的调用者从外部传进来的被装饰对象,调用者只想让你把他给你的对象加强一下,装饰一下. 代理模式使用的是代理对象在自己的构造方法里面new的一个被代理类的对象,不是调用者传入的,调用者不知道你找了其他人,他也不关心这些事,只要你把事情做好就行了,代理模式这边对其调用类进行了隐藏的操作,外部不知道你的具体调用的类的方法,而装饰器模式外面是可以感知到的。
外观模式
外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口,就是在一个类里面去对里面所有的类进行处理,写点伪代码
public class CPU {
public void startup(){
System.out.println("cpu startup!");
}
public void shutdown(){
System.out.println("cpu shutdown!");
}
}
public class Disk {
public void startup(){
System.out.println("disk startup!");
}
public void shutdown(){
System.out.println("disk shutdown!");
}
}
public class Memory {
public void startup(){
System.out.println("memory startup!");
}
public void shutdown(){
System.out.println("memory shutdown!");
}
}
public class Computer {
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer(){
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void startup(){
System.out.println("start the computer!");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("start computer finished!");
}
public void shutdown(){
System.out.println("begin to close the computer!");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("computer closed!");
}
}
可以看出,把一系列的不同的类的方法进行封装,好比computer在startup的时候,需要调用cpu,memory,disk的startup方法,常规的话可能是对象套对象去进行方法的调用,而使用外观模式的话就减少了类之间的耦合,使类之间的关联更小
桥接模式
桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。 自己设置持有的引用
这边来用代码带入
public interface Sourceable {
public void method();
}
public abstract class Bridge {
private Sourceable source;
public void method(){
source.method();
}
public Sourceable getSource() {
return source;
}
public void setSource(Sourceable source) {
this.source = source;
}
}
/**
* 定义一个桥,持有Sourceable的一个实例
*/
public class MyBridge extends Bridge {
@Override
public void method(){
// 重点在这边,具体的调用方法 在调用之前先对这个source进行set 即setSource(Sourceable source),传入合适的sourceable的具体实现
getSource().method();
}
}
看上述代码,重要的是在调用之前先设置具体的实现类,即先实例化接口的具体实现,在调用之前先用setSource这个方法来进行对象的引入,然后再进行对象的调用
组合模式
组合模式(Composite Pattern)有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。组合模式让你可以优化处理递归或分级数据结构。有许多关于分级数据结构的例子,使得组合模式非常有用武之地。关于分级数据结构的一个普遍性的例子是你每次使用电脑时所遇到的:文件系统。文件系统由目录和文件组成。每个目录都可以装内容。目录的内容可以是文件,也可以是目录。按照这种方式,计算机的文件系统就是以递归结构来组织的。如果你想要描述这样的数据结构,那么你可以使用组合模式Composite。也可以考虑三级联动这种关系类型的数据
public class TreeNode {
private String name;
private TreeNode parent;
private Vector<TreeNode> children = new Vector<TreeNode>();
public TreeNode(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TreeNode getParent() {
return parent;
}
public void setParent(TreeNode parent) {
this.parent = parent;
}
//添加孩子节点
public void add(TreeNode node){
children.add(node);
}
//删除孩子节点
public void remove(TreeNode node){
children.remove(node);
}
//取得孩子节点
public Enumeration<TreeNode> getChildren(){
return children.elements();
}
}
上面这些代码就是组合模式的大概体现
享元模式
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。如jdbc连接池就是享元模式和工厂模式一起来使用的,通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能,这边上个建议连接池的代码
public class ConnectionPool {
private Vector<Connection> pool;
/*公有属性*/
private String url = "jdbc:mysql://localhost:3306/test";
private String username = "root";
private String password = "root";
private String driverClassName = "com.mysql.jdbc.Driver";
private int poolSize = 100;
private static ConnectionPool instance = null;
Connection conn = null;
/*构造方法,做一些初始化工作*/
private ConnectionPool() {
pool = new Vector<Connection>(poolSize);
for (int i = 0; i < poolSize; i++) {
try {
Class.forName(driverClassName);
conn = DriverManager.getConnection(url, username, password);
pool.add(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/* 返回连接到连接池 */
public synchronized void release() {
pool.add(conn);
}
/* 返回连接池中的一个数据库连接 */
public synchronized Connection getConnection() {
if (pool.size() > 0) {
Connection conn = pool.get(0);
pool.remove(conn);
return conn;
} else {
return null;
}
}
}
行为型模式
行为型的设计模式主要有十一个,大概可以分成四个类别
-
父类与子类
- 策略模式
- 模板方法模式
-
两个类之间
- 观察者模式
- 迭代子模式
- 责任链模式
- 命令模式
-
类的状态
- 备忘录模式
- 状态模式
-
通过中间类
- 访问者模式
- 中介者模式
- 解释器模式
策略模式
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数,也就是用户来进行选择具体的实现 不用去考虑实现内容,就好比支付方式用户可以选择微信支付,支付宝支付,银联支付等等
定义一个支付的接口
public interface Pay {
public void pay();
}
这边写几个具体的实现
public class AliPay implements Pay {
@Override
public void pay() {
System.out.println("this is AliPay");
}
}
public class WeChatPay implements Pay {
@Override
public void pay() {
System.out.println("this is WeChatPay");
}
}
定义选择的枚举
public enum PayStrategyEnum {
ALI_PAY(new AliPay()),
WECHAT_PAY(new WeChatPay());
private Pay pay;
public Pay getPay() {
return pay;
}
public void setPay(Pay pay) {
this.pay = pay;
}
PayStrategyEnum(Pay pay){
this.pay = pay;
}
}
这边是测试用例
public class PayStrategyTest {
public static void main(String[] args) {
PayStrategyEnum.ALI_PAY.getPay().pay();
}
}
这边可以看到用户不用去关心具体的实现细节 只需要去进行选择用哪种支付方式就行 使用支付宝去进行支付还是选择微信来进行支付
模板方法模式
模板方法模式,就是指:一个抽象类中,有一个主方法,这个主方法是有着具体的实现的,再定义1…n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用,就好比不同的流水线 有着同样的工序,可以把这些进行抽象,这个同样工序的在抽象类里面定义好,剩下不同的工序可以有着自己的定义去实现
public abstract class AbstractCalculator {
/*主方法,实现对本类其它方法的调用*/
public final int calculate(String exp,String opt){
int array[] = split(exp,opt);
return calculate(array[0],array[1]);
}
/*被子类重写的方法*/
abstract public int calculate(int num1,int num2);
public int[] split(String exp,String opt){
String array[] = exp.split(opt);
int arrayInt[] = new int[2];
arrayInt[0] = Integer.parseInt(array[0]);
arrayInt[1] = Integer.parseInt(array[1]);
return arrayInt;
}
}
public class Plus extends AbstractCalculator {
@Override
public int calculate(int num1,int num2) {
return num1 + num2;
}
}
这个是测试的demo
public class TemplateMethodTest {
public static void main(String[] args) {
String exp = "8+8";
AbstractCalculator cal = new Plus();
int result = cal.calculate(exp, "\\+");
System.out.println(result);
}
}
观察者模式
观察者模式其实很好去理解,看看这边下面写的demo应该就能明白,MySubject类就是我们的主对象,Observer1和Observer2是依赖于MySubject的对象,当MySubject变化时,Observer1和Observer2必然能收到它变化的通知。AbstractSubject类中定义着需要监控的对象列表,不一定非要用列表去实现,可以考虑别的储存方式,如果是多机器部署服务的话可以考虑使用nosql数据库来进行存储,可以对其进行修改:增加或删除被监控对象,当MySubject变化时,负责通知在列表内存在的对象,
还是先去定义两个接口
public interface Observer {
public void update();
}
public interface Subject {
/*增加观察者*/
public void add(Observer observer);
/*删除观察者*/
public void del(Observer observer);
/*通知所有的观察者*/
public void notifyObservers();
/*自身的操作*/
public void operation();
}
定义的中间抽象类
public abstract class AbstractSubject implements Subject {
private Vector<Observer> vector = new Vector<Observer>();
@Override
public void add(Observer observer) {
vector.add(observer);
}
@Override
public void del(Observer observer) {
vector.remove(observer);
}
@Override
public void notifyObservers() {
Enumeration<Observer> enumo = vector.elements();
while(enumo.hasMoreElements()){
enumo.nextElement().update();
}
}
}
具体的类
public class MySubject extends AbstractSubject {
@Override
public void operation() {
System.out.println("update self!");
notifyObservers();
}
}
public class Observer1 implements Observer {
@Override
public void update() {
System.out.println("observer1 has received!");
}
}
public class Observer2 implements Observer {
@Override
public void update() {
System.out.println("observer2 has received!");
}
}
public class ObserverTest {
public static void main(String[] args) {
Subject sub = new MySubject();
sub.add(new Observer1());
sub.add(new Observer2());
sub.operation();
}
}
当把观察者添加到这里面之后,当当前发生变动的时候会有通知通知到观察者
迭代器模式
迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。这边我们先定义两个接口
public interface Collection {
public Iterator iterator();
/*取得集合元素*/
public Object get(int i);
/*取得集合大小*/
public int size();
}
public interface Iterator {
//前移
public Object previous();
//后移
public Object next();
public boolean hasNext();
//取得第一个元素
public Object first();
}
两个实现类
public class MyCollection implements Collection {
public String string[] = {"A","B","C","D","E"};
@Override
public Iterator iterator() {
return new MyIterator(this);
}
@Override
public Object get(int i) {
return string[i];
}
@Override
public int size() {
return string.length;
}
}
public class MyIterator implements Iterator {
private Collection collection;
private int pos = -1;
public MyIterator(Collection collection){
this.collection = collection;
}
/**
* 获取之前的 这个类似于回退
*/
@Override
public Object previous() {
if(pos > 0){
pos--;
}
return collection.get(pos);
}
/**
* 获取下一个元素 要是有的话 之前要调用hasnext()来确 * 定是否有元素
*/
@Override
public Object next() {
if(pos<collection.size()-1){
pos++;
}
return collection.get(pos);
}
/**
* 是否有元素
*/
@Override
public boolean hasNext() {
if(pos<collection.size()-1){
return true;
}else{
return false;
}
}
/**
* 获取第一个元素 这样的话 之后的next不是原先的 是从 * 第一个之后开始进行计算
*/
@Override
public Object first() {
pos = 0;
return collection.get(pos);
}
}
测试demo
public class Test {
public static void main(String[] args) {
Collection collection = new MyCollection();
Iterator it = collection.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
责任链模式
责任链模式,有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象,写个自身的循环链
接口
public interface Handler {
public void operator();
}
抽象类
public abstract class AbstractHandler {
private Handler handler;
public Handler getHandler() {
return handler;
}
public void setHandler(Handler handler) {
this.handler = handler;
}
}
具体的干活类
public class MyHandler extends AbstractHandler implements Handler {
private String name;
public MyHandler(String name) {
this.name = name;
}
@Override
public void operator() {
System.out.println(name+"deal!");
if(getHandler()!=null){
getHandler().operator();
}
}
}
测试Demo
public class Test {
public static void main(String[] args) {
MyHandler h1 = new MyHandler("h1");
MyHandler h2 = new MyHandler("h2");
MyHandler h3 = new MyHandler("h3");
h1.setHandler(h2);
h2.setHandler(h3);
h1.operator();
}
}
以上demo就是一层调一层 这个和之前的外观模式感觉有点相反的意思,可能是个人理解能力问题
命令模式
命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。 demo中 Invoker是调用者(司令员),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象 就好比工作中项目经理下发命令你去进行执行
先定义接口
public interface Command {
public void exe();
}
public class MyCommand implements Command {
private Receiver receiver;
public MyCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void exe() {
receiver.action();
}
}
public class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void action(){
command.exe();
}
}
public class Receiver {
public void action(){
System.out.println("command received!");
}
}
测试demo
public class CommandTest {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command cmd = new MyCommand(receiver);
Invoker invoker = new Invoker(cmd);
invoker.action();
}
}
其实这个个人感觉跟适配器模式有点像 持有对象 让所持有的对象去干具体的事情
备忘录模式
主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象,个人觉得叫备份模式更形象些,通俗的讲下:假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。demo中,Original类是原始类,里面有需要保存的属性value及创建一个备忘录类,用来保存value值。Memento类是备忘录类,Storage类是存储备忘录的类,持有Memento类的实例
直接上代码 这个没有怎么细看
public class Memento {
private String value;
public Memento(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
public class Original {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Original(String value) {
this.value = value;
}
public Memento createMemento(){
return new Memento(value);
}
public void restoreMemento(Memento memento){
this.value = memento.getValue();
}
}
public class Storage {
private Memento memento;
public Storage(Memento memento) {
this.memento = memento;
}
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
测试Demo
public class MementoTest {
public static void main(String[] args) {
// 创建原始类
Original origi = new Original("egg");
// 创建备忘录
Storage storage = new Storage(origi.createMemento());
// 修改原始类的状态
System.out.println("初始化状态为:" + origi.getValue());
origi.setValue("niu");
System.out.println("修改后的状态为:" + origi.getValue());
// 回复原始类的状态
origi.restoreMemento(storage.getMemento());
System.out.println("恢复后的状态为:" + origi.getValue());
}
}
这个有空的话可以好好看看 有空的话
状态模式
当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。demo中,State类是个状态类,Context类可以实现切换
直接上代码
/**
* 状态类的核心类
* 2012-12-1
* @author erqing
*
*/
public class State {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public void method1(){
System.out.println("execute the first opt!");
}
public void method2(){
System.out.println("execute the second opt!");
}
}
/**
* 状态模式的切换类 2012-12-1
* @author erqing
*
*/
public class Context {
private State state;
public Context(State state) {
this.state = state;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public void method() {
if (state.getValue().equals("state1")) {
state.method1();
} else if (state.getValue().equals("state2")) {
state.method2();
}
}
}
测试Demo
public class Test {
public static void main(String[] args) {
State state = new State();
Context context = new Context(state);
//设置第一种状态
state.setValue("state1");
context.method();
//设置第二种状态
state.setValue("state2");
context.method();
}
}
访问者模式
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。 适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:1、新功能会不会与现有功能出现兼容性问题?2、以后会不会再需要添加?3、如果类不允许修改代码怎么办?面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法进行解耦
还是先定义两个接口
/**
* 一个Visitor类,存放要访问的对象,
*/
public interface Visitor {
public void visit(Subject sub);
}
/**
* Subject类,accept方法,接受将要访问它的对象,getSubject()获取将要被访问的属性,
*/
public interface Subject {
public void accept(Visitor visitor);
public String getSubject();
}
public class MyVisitor implements Visitor {
@Override
public void visit(Subject sub) {
System.out.println("visit the subject:"+sub.getSubject());
}
}
public class MySubject implements Subject {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
@Override
public String getSubject() {
return "love";
}
}
测试Demo
public class VisitorTest {
public static void main(String[] args) {
Visitor visitor = new MyVisitor();
Subject sub = new MySubject();
sub.accept(visitor);
}
}
中介者模式
中介者模式也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。如果使用中介者模式,只需关心和Mediator类的关系,具体类类之间的关系及调度交给Mediator就行,这有点像spring容器的作用。 demo中 User类统一接口,User1和User2分别是不同的对象,二者之间有关联,如果不采用中介者模式,则需要二者相互持有引用,这样二者的耦合度很高,为了解耦,引入了Mediator类,提供统一接口,MyMediator为其实现类,里面持有User1和User2的实例,用来实现对User1和User2的控制。这样User1和User2两个对象相互独立,他们只需要保持好和Mediator之间的关系就行,剩下的全由MyMediator类来维护!直接上代码
public interface Mediator {
public void createMediator();
public void workAll();
}
public abstract class User {
private Mediator mediator;
public Mediator getMediator(){
return mediator;
}
public User(Mediator mediator) {
this.mediator = mediator;
}
public abstract void work();
}
public class User1 extends User {
public User1(Mediator mediator){
super(mediator);
}
@Override
public void work() {
System.out.println("user1 exe!");
}
}
public class User2 extends User {
public User2(Mediator mediator){
super(mediator);
}
@Override
public void work() {
System.out.println("user2 exe!");
}
}
public class MyMediator implements Mediator {
private User user1;
private User user2;
public User getUser1() {
return user1;
}
public User getUser2() {
return user2;
}
@Override
public void createMediator() {
user1 = new User1(this);
user2 = new User2(this);
}
@Override
public void workAll() {
user1.work();
user2.work();
}
}
测试Demo
public class MediatorTest {
public static void main(String[] args) {
Mediator mediator = new MyMediator();
mediator.createMediator();
mediator.workAll();
}
}
解释器模式
主要应用在OOP开发中的编译器的开发中,所以适用面比较窄 解释器模式用来做各种各样的解释器,如正则表达式等的解释器等等demo中 Context类是一个上下文环境类,Plus和Minus分别是用来计算的实现
public interface Expression {
public int interpret(Context context);
}
public class Minus implements Expression {
@Override
public int interpret(Context context) {
return context.getNum1()-context.getNum2();
}
}
public class Plus implements Expression {
@Override
public int interpret(Context context) {
return context.getNum1()+context.getNum2();
}
}
public class Context {
private int num1;
private int num2;
public Context(int num1, int num2) {
this.num1 = num1;
this.num2 = num2;
}
public int getNum1() {
return num1;
}
public void setNum1(int num1) {
this.num1 = num1;
}
public int getNum2() {
return num2;
}
public void setNum2(int num2) {
this.num2 = num2;
}
}
测试Demo
public class InterpreterTest {
public static void main(String[] args) {
// 计算9+2-8的值
int result = new Minus().interpret(new Context(new Plus()
.interpret(new Context(9, 2)), 8));
System.out.println(result);
}
}