Java设计模式(上)

Java设计模式(上)

设计模式类型个数内容
创建型模式5工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
结构型模式7适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
行为型模式11策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

设计模式之间的关系


一、设计模式六大原则

  1. 开闭原则 Open Close Principle

    内容:开闭原则不允许修改原有代码,如果想要拓展程序,添加新功能,需要通过接口或者抽象类来实现。
    好处:使程序拓展性更好,易于维护和升级。

  2. 里氏代换原则 Liskov Substitution Principle

    内容:里氏代换原则是对面向对象中,子类与父类使用的诠释。任何父类出现的地方,其子类一定可以出现,同时,只有当子类可以完全替换掉父类,且程序功能不会受到影响时,父类才算真正被复用,子类可以在父类的基础上添加新的行为。里氏代换原则是对开闭原则的补充。想要实现开闭原则,需要用到抽象化,而里氏代换原则则是对实现抽象化的步骤规范。

  3. 依赖倒转原则 Dependence Inversion Principle

    内容:开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。

  4. 接口隔离原则 Interface Segregation Principle

    内容:提倡一个类使用多个隔离的接口,而尽量减少使用单个接口的次数。
    优点:降低类与类之间的耦合性。(降低依赖,降低耦合)

  5. 迪米特原则/最少知道原则 Demeter Principle

    内容:最少知道,一个实体应该尽量减少与其他实体之间发生相互作用,这样使得程序的功能模块相对独立,降低耦合。

  6. 合成复用原则 Composite Reuse Principk

    内容:减少使用继承,尽量使用合成/聚合。
    原因:继承在代码编写时便决定好了,不便于随时修改,同时子类对父类依赖太强,父类的变动会对子类有很大的影响。继承这种依赖关系限制了灵活性,并最终限制了复用性。

二、23种设计模式

1.创建型模式

(1)工厂方法模式 Factory Method

1. 普通工厂模式 : 建立一个工厂类,对实现了同一接口的类进行管理、实现。

创建接口(发送信息接口)

public interface Sender {
    public void send(){
        System.out.println("this is a interface");
    }
}

创建两个实现类

public class EmailSender implements Sender{
    public void send(){
        System.out.println("this is Email");
    }
}

public class MessageSender implements Sender{
    public void send(){
        System.out.println("this is Message");
    }
}

创建工厂类

public SenderFactory{
    public Sender createSender(String type){
        if(type.equals("Email")){
            return new EmailSender();
        } else if(type.equals("Message")){
            return new MessageSender();
        } else {
            System.out.println("输入有误!!");
            return null;
        }
    }
}

main函数

public static void main(String[] args){
    SenderFactory senderFactory = new SenderFactory();
    Sender sender = senderFactory.createSender("Email");
    sender.send();
}

输出结果:this is Email

2. 多个工厂模式 : 多个工厂模式是对单普通工厂模式的改进,普通工厂模式中如果字符串出错,则直接返回一个null值,多个工厂模式内则有多个对应的函数,能直接返回相应的对象。

对工厂类进行小幅修改

public class SenderFactory(){
    public Sender createEmailSender(){
        return new EmailSender();
    }
    public Sender createMessageSender(){
        return new MessageSender();
    }
}

//测试类
public static void main(String[] args){
    SenderFactory senderFactory = new SenderFactory();
    Sender sender = senderFactory.createEmailSender();
    sender.sned();
}

//输出:this is Email

3.静态工厂方法模式 : 将上述工厂类方法设为静态,调用时不再需要创建对象。

public class SenderFactory(){
    public static Sender createEmailSender(){
        return new EmailSender();
    }
    public static Sender createMessageSender(){
        return new MessageSender();
    }
}

//测试类
public static void main(String[] args){
    Sender sender = SenderFactory.createEmailSender();
    sender.sned();
}

//输出:this is Email

工厂方法模式适用范围:

  • 大量对象需要创建
  • 对象都需实现同一方法(功能)

小总结:第一种模式如果字符串输入错误则失败,第三种模式相当于第二种,一般情况下选择第三种模式。

(2)抽象工厂模式 Abstract Method

抽象工厂模式是对工厂方法模式的补充,工厂方法模式的缺点显而易见,如果想要新增功能,必须修改工厂类,这违背了开闭原则。抽象工厂模式则是建立多了工厂类,他们处理和功能对象一样,继承同一个接口,每次需要新增功能时只需要新加一个工厂类即可。下面是代码演示:

创建接口

public interface Sender {
    public void send();
}

创建两个实现类

public class EmailSender implements Sender{
    public void send(){
        System.out.println("this is Email");
    }
}

public class MessageSender implements Sender{
    public void send(){
        System.out.println("this is Message");
    }
}

创建工厂类实现接口

public interface Factory {
    public Sender create();
}

创建工厂类

public class EmailFactory implements Factory{
    public static Sender create(){
        return new EmailSender();
    }
}

public class MessageFactory implements Factory{
    public static Sender create(){
        return new MessageSender();
    }
}

测试类:

public static void main(String[] args){
    Sender sender = EmailFactory.create();
    sender.send();
}

//输出:this is Email

好处 : 其实好处显而易见,如果想增加功能,只需要添加一个工厂类,大幅度降低了耦合度。

(3)单例模式 Singleton

单例类算是一个用处比较广泛的方法。在java应用中,单例类能够保证在一个Java虚拟机里只存在一个实例,这样的好处有:

  1. 某一些类的创建比较麻烦,省去了一个大型类的创建开销。

  2. 减少了new操作, 降低了系统内存使用的频率。

  3. 对于某些掌有控制权的类,如果创建了多个实例容易造成混淆,只有创建单例类才能保证单一控制类。

但是,单例类也有着自己的缺陷,下面是一个简单的单例类:

public class Singleton{
    //私有化对象,防止被引用,同时赋值为null,延迟加载,提高效率
    private Singleton singleton = null;

    //私有化构造函数,防止实例化
    private Singleton(){}

    //通过该函数获取Singleton对象
    public static Singleton getSingleton(){
        if(singleton == null) 
            singleton = new Singleton();
        return singleton;
    }
}

该单例类能够基本满足我们的要求,但是,一旦涉及到了多线程,便会出现问题,这一部分涉及到了Java中的单例类,之后单独拿一篇文章来写这个知识点,这里便带过一下,贴一下基本完美的单例类:

static方式实现线程安全:

public class Singleton{
    //私有化对象,防止被引用,同时赋值为null,延迟加载,提高效率
    private static Singleton singleton = new Singleton();

    //私有化构造函数,防止实例化
    private Singleton(){}

    //通过该函数获取Singleton对象
    public static Singleton getSingleton(){
        return Singleton.singleton;
    }
}

内部类方式:

public class Singleton{
    //私有化构造函数,防止实例化
    private Singleton(){}

    //内部类将singleton实例化
    private class CreateSingleton{
        private static Singleton singleton = new Singleton();
    }

    //通过该函数获取Singleton对象
    public static Singleton getSingleton(){
        return CreateSingleton.singleton;
    }
}

创建Singleton对象时单独加synchronized关键字:

public class Singleton{
    //私有化对象,防止被引用,同时赋值为null,延迟加载,提高效率
    private Singleton singleton = null;

    //私有化构造函数,防止实例化
    private Singleton(){}

    private static synchronized void syncInit(){
        if(singleton == null)
            singleton = new Singleton();
    }

    //通过该函数获取Singleton对象
    public static Singleton getSingleton(){
        if(singleton == null) 
            syncInit();
        return singleton;
    }
}

单例模式大抵如此,具体的之后另一篇文章详细介绍。

(4)建造者模式 Builder

建造者模式和工厂模式也比较像,相对于建造者模式,工厂模式更适合简单一点的类,如果是很复杂的类,大量的功能组合起来,则需要用到建造者模式。

由于笔者在构造这模式这一部分理解不太透彻,看了许多代码也没有把握到精髓,这里便根据个人理解,在前面例子的基础上修改一番。

创建接口,同时在上面两个类的基础上再添加几个功能类:

public class ImageSender{
    public void send(){
        System.out.println("this is Image");
    }
}

public class VideoSender{
    public void send(){
        System.out.println("this is Video");
    }
}

添加FinalSender类:

public class FinalSender{
    private ArrayList<Sender> senders = new ArrayList<Sender>();

    public void addFunction(ArrayList<Sender> senders){
        this.senders = senders;
    }

    public void show(){
        System.out.println("my function :");
        for(int i = 0; i < senders.size(); i++){
            System.out.println(senders.get(i).send(););
        }
    }
}

工厂类修改为构造者类:

public class Builder{
    public FinalSender build(ArrayList<Sender> functions){
        FinalSender finalSender =  new FinalSender();
        finalSender.addFunction(functions);
        return finalSender;
    }
}

测试类:

public static void main(String[] args){
    Builder builder = new Builder();

    ArrayList<Sender> functionA = new ArrayList<Sender>();
    ArrayList<Sender> functionB = new ArrayList<Sender>();

    functionA.add(new EmailSender());
    functionA.add(new ImageSender());

    functionB.add(new MessageSender());
    functionB.add(new VideoSender());

    FinalSender ASender = builder.build(functionA);
    FinalSender BSender = builder.build(functionB);
    ASender.show();
    BSender.show();
}

/*输出:my function :
this is Email
this is Image
my function :
this is Message
this is Video*/

构造者模式特点很明显,也是十分符合设计模式的原则,低耦合,低依赖。

  • 首先,构造者模式将一个复杂的类分解成多个功能,使结构更加清晰。
  • 同时将过程与结果分离,能够使用相同的过程创造不同的结果。
  • 最后,开闭原则,添加新功能完全不必修改之前代码,直接添加新功能(ArrayList)构造新的FinalSender即可。
(5)原型模式 Prototype

当一个类需要重复创建时,同时又要保持效率,这时便可以考虑原型模式。原型模式实现了一个方法,用来克隆他自己。当直接创建对象的代价比较大时,则采用这种模式。

先看代码,克隆分为深克隆和浅克隆:

  • 浅克隆:将一个对象复制一个,所有基本数据类型的变量都会被重新创建,而引用类型则还是指向原对象所指的。
  • 深克隆:将一个对象复制一个,所有基本数据类型和引用类型的变量都会被重新创建。

代码演示:

public class Prototype implements Cloneable{
    //浅克隆
    public Object clone() throws CloneNotSupportedException{
        Prototype prototype = (Prototype)super.clone();
        return prototype; 
    }

    //深克隆
    public Object deepClone(){
        //将当前对象写入二进制流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        //从二进制流读出对象
        ByteArrayInputStream bis = new ByteArrayInputStream(bos);
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();
    }
}

这里有一个问题,我看了网上许多代码,关于克隆的实现全是一个类实现Cloneable接口,之后其他类继承这个类,便能拥有克隆的功能,但是,如果原型类本身就拥有父类又该怎么办,而且根据合成复用原则,这里最好用接口实现克隆,但是接口的话,又不能提高clone代码的复用性,这一点有待研究。

2.结构型模式

结构型模式

(6)适配器模式 Adapter

适配器模式我在网上看了许多代码,一开始确实有许多不懂的地方,确实不知道这个适配器有什么作用,这里我尽可能讲清楚。适配器模式分为三种:类的适配器模式、对象的适配器模式、借口的适配器模式

  • 类的适配器模式

核心思想:现在已有一个接口 i,一个类 A,现在想要 A 能实现接口 I,则新建一个类 B(Adapter适配器),B 继承 A 实现 i,现在 A a = new B(); 便能让 A 实现 i。代码:

//原有类A
public class A{
    public void oldMethod(){
        System.out.println("A's method");
    }
}

//接口i
public interface I {
    public void newMethod();
}

//适配器B
public class B extends A implements I{
    public void newMethod(){
        System.out.println("B's method");
    }
}

//测试类
public static void main(String[] args){
    A a = new B();
    //I i = new B();
    a.oldMethod();
    a.newMethod();
}

/*输出:A's method
B's method*/
  • 对象的适配器模式

第二种大体上和第一种相似,第一种是将类转换以实现新的接口,第二种则是将对象转换以实现新的接口。代码:

只需修改适配器 B

public class B implements I{
    private A a;

    public B(A a){
        this.a = a;
    }

    public void newMethod(){
        System.out.println("B's method");
    }

    public void oldMethod(){
        a.oldMethod();
    }
}

//测试类
public static void main(String[] args){
    A a = new A();
    I i = new B(a);
    i.oldMethod();
    i.newMethod();
}

/*输出:A's method
B's method*/
  • 接口的适配器
    接口适配器在Java源码里用了许多,核心思想就是实现一个接口可能要实现其全部方法,但是对于一个大型接口来说,其中许多方法我们并不需要一起实现,这里便引入一个抽象类,要实现接口时直接继承这个抽象类,避免造成资源的浪费。
//接口I
public interface I{
    public void oldMethod();
    public void newMethod();
}

//抽象类B
public abstract class B implements I {
    public void oldMethod(){}
    public void newMethod(){}
}

//实现类 A1
public class A1 extends B{
    public void oldMethod(){
        System.out.println("old method");
    }
}

//实现类 A2
public class A2 extends B{
    public void newMethod(){
        System.out.println("new method");
    }
}

//测试类
public static void main(String[] args){
    A1 a1 = new A1();
    A2 a2 = new A2();
    A1.oldMethod();
    A2.newMethod();
}

/*输出:old method
new method*/

小总结:

  1. 类的适配器:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
  2. 对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
  3. 接口的适配器模式:不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
(7)装饰模式 Decorator

一开始,我感觉装饰模式和对象的适配器模式是基本一样的,后来发现了差别,这里记录一下:对象的适配器模式是让一个全新的对象通过适配器实现一个接口,以达到添加新功能的作用。而装饰模式则装饰者和被装饰者都需实现同一个接口,在此基础上,通过装饰者来往被装饰者中添加新功能。

一层装饰模式:

//定义接口I
public interface I(){
    public void watch();
}

//定义实现类
public class A implements I{
    public void watch(){
        System.out.println("I can watch!");
    }
} 

现在想在A的基础上加强watch功能,添加装饰类:

public class Decorator implements I{
    private A a;

    public Decorator(A a){
        this.a = a;
    }

    public void watch(){
        System.out.println("I can watch!");
        speak();
    }

    public void speak(){
        System.out.println("I also can speak.");
    }
}

//测试类:
public static void main(String[] args){
    A a = new A();
    I i = new Decorator(a);
    a.watch();
    i.watch();
}

/*输出:I can watch!
I can watch!
I also can speak.*/

由此可看出,想要在A的基础上添加新功能,可以无限制的增加装饰类,不过缺点也显而易见,多层装饰会比较复杂。

(8)代理模式 Proxy

核心思想:为其他对象提供一种代理以控制这个对象的访问。
代理模式理解起来有点抽象,因为看上去和适配器模式、装饰模式长得好像,可能在实际应用中才能有更加深入的理解。

//定义接口
public interface I{
    public void method();
}

//定义实现类(被代理类)
public class A implements I{
    public void method(){
        System.out.println("this is old method!");
    }
}

//定义代理类
public class B implements I{
    private A a;

    public B(){
        this.a = new A();
    }

    public void method(){
        a.method();
        System.out.println("and new method!");
    }
}

//测试类
public static void main(String[] args){
    I i = new B();
    i.method();
}

/*输出:this is old method!
this is old method!
and new method!*/

注意事项:

  • 和适配器模式的区别:适配器模式主要改变应用对象的接口,而代理模式不能改变所代理对象的接口。
  • 和装饰模式的区别:装饰模式主要为了增加功能,而代理模式是为了加以控制。
    装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模 式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。——装饰模式与代理模式的区别

缺点:

  • 由于在客户与类之间增加了代理类,所以有些代理模式会造成请求的处理速度变慢。
  • 实现代理模式需要增加额外的工作,有的代理模式实现起来十分复杂。
(9)外观模式 Facade

外观模式主要作用也是低耦合,将几个有关系的类拆分开,其关系放在另一个类中,这要要修改他们关系时只要修改一个关系类就行了,不过这个会违反开闭原则。

//接口
public interface I{
    public void bagin();
}

//实现类
public class Teacher implements I{
    public void bagin(){
        System.out.println("teacher is born");
    }
}

public class Student implements I{
    public void bagin(){
        System.out.println("student is born");
    }
}

//关系类
public class A{
    private Teacher teacher;
    private Student student;

    public A(){
        teacher = new Teacher();
        student = new Student();
    }

    public void born(){
        teacher.born();
        student.born();
    }
}

//测试类
public static void main(String[] args){
    A a = new A();
    a.born();
}

/*输出:teacher is born
student is born*/
(10)桥接模式 Bridge

桥接模式主要用于将事物和其具体实现分开,最终目的:将抽象化与实现化解耦,使二者可以独立变化。离我们最近的便是连接数据库的JDBC桥,不管是换数据库类型还是换账号密码,要改动的地方其实特别少,原因便是JDBC提供了统一的接口。具体可以看代码

定义接口

public interface I {
    public void method();
}

定义实现类:

public A implements I{
    public void method(){
        System.out.println("This is A!");
    }
}
public B implements I{
    public void method(){
        System.out.println("This is B!");
    }
}

定义抽象类,也就是桥,同时定义一个继承他的类:

public abstract class Bridge{
    private I i;

    public void method(){
        I.method();
    }

    public I getI(){
        return i;
    }

    public void setI(I i){
        this.i = i;
    }
}

public class myBridge extends Bridge{
    public void method(){
        getI.method();
    }
}

测试类:

public class BridgeTest {  

    public static void main(String[] args) {  

        Bridge bridge = new MyBridge();  

        //调用第一个对象
        I a = new A();  
        bridge.setSource(a);  
        bridge.method();  

        //调用第二个对象
        I b = new B();  
        bridge.setSource(b);  
        bridge.method();  
    }  
}
/*输出:This is A!
This is B!*/

桥接模式大致就是这样,下图则是JDBC大致的工作流程,放在这里以作参考。

JDBC工作流程

(11)组合模式 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();  
    }  
}

public class Tree {  

    TreeNode root = null;  

    public Tree(String name) {  
        root = new TreeNode(name);  
    }  

    public static void main(String[] args) {  
        Tree tree = new Tree("A");  
        TreeNode nodeB = new TreeNode("B");  
        TreeNode nodeC = new TreeNode("C");  

        nodeB.add(nodeC);  
        tree.root.add(nodeB);  
        System.out.println("build the tree finished!");  
    }  
} 

其实代码也能看得出,组合模式主要用于将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。

(12)享元模式 Flyweight

享元模式的主要目的是实现对象的共享,即共享池。这一点其实都比较熟悉,比如数据库连接中的连接池、多线程当中的线程池,都属于这一类用法,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象

这里看一下数据库连接池的代码:

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;  
        }  
    }  
}

享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。

3.行为型模式

行为型模式


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值