常用设计原则和设计模式

  • Post author:
  • Post category:其他



目录


1. 常用的设计原则


2. 常用的设计模式


2.1 单例设计模式


2.1.1 饿汉模式


2.1.2 懒汉模式


2.2 工厂方法模式


2.2.1 普通工厂模式:


2.2.2 多个工厂方法模式:


2.2.3 静态工厂方法模式:


2.3 抽象工厂模式


2.4 装饰器模式


2.5 代理模式


2.6 模板设计模式


1. 常用的设计原则


  • 开闭原则

    (Open Close Principle)

对扩展开放对修改关闭,为了使程序的扩展性好,易于维护和升级。


  • 里氏代换原则

    (Liskov Substitution Principle)

任何基类可以出现的地方,子类一定可以出现,多使用多态的方式。


  • 依赖倒转原则

    (Dependence Inversion Principle)

尽量多依赖于抽象类或接口而不是具体实现类,对子类具有强制性和规范性


  • 接口隔离原则

    (Interface Segregation Principle)

尽量多使用小接口而不是大接口,避免接口的污染,降低类之间耦合度。


  • 迪米特法则

    (最少知道原则)(Demeter Principle)

一个实体应当尽量少与其他实体之间发生相互作用,使系统功能模块相对独立。高内聚,低耦合。


  • 合成复用原则

    (Composite Reuse Principle)

尽量多使用合成/聚合的方式,而不是继承的方式。


2. 常用的设计模式

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

设计模式就是一种用于固定场合的固定套路。


  • 创建型模式 –

    单例设计模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式

  • 结构型模式 –

    装饰器模式、代理模式、适配器模式、桥接模式、组合模式、外观模式、享元模式

  • 行为型模式 –

    模板设计模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、接解释器模式、状态模式、策略模式、职责链模式、访问者模式。

2.1 单例设计模式

单例设计模式主要分为:饿汉式 和 懒汉式,懒汉式需要对多线程进行同步处理。



2.1.1 饿汉模式

(立即加载, 线程安全, 推荐):类加载慢, 运行时获取对象快

/*
   饿汉式
 */
public class Singleton {
     // 2. 使用private static 共同修饰对象的引用
    private static Singleton sin = new Singleton();

     //1. 私有化构 方法 使用private关键字修饰
    private Singleton(){ }

   // 3.  提供公有的get方法将对象返回出去,用public static共同修饰
    public static Singleton getInstance(){
        return sin;
    }

}


2.1.2 懒汉模式

(延迟加载, 非线程安全):类加载快, 运行时获取对象慢

/*
   懒汉式--双重锁(适用于多线程)
 */

public class Singleton {

    // 2.声明本类类型的引用指向本类类型的对象并使用private static关键字修饰
    private static Singleton sin = null;

    // 1.私有化构造方法,使用private关键字修饰
    private Singleton() {}

    // 3.提供公有的get方法负责将上述对象返回出去,使用public static关键字修饰
    public static Singleton getInstance() {
      
        if (null == sin) {
            synchronized (Singleton.class) {
                if (null == sin) {
                    sin = new Singleton();
                }
            }
        }
        return sin;
    }
}

2.2 工厂方法模式

  • 2.2.1 普通工厂模式:

普通工厂方法模式就是建立一个工厂类,对实现了同一接口的不同实现类进行实例的创建。


Send接口:

public interface Sender {
    // 自定义抽象方法来描述发送的行为
    void send();
}


Send实现类:

/*
   短信形式发送...
 */
public class SmsSender implements Sender {
    @Override
    public void send() {
        System.out.println("正在发送短信...");
    }
}

/*
   邮件形式发送...
 */
public class MailSender implements Sender {
    @Override
    public void send() {
        System.out.println("正在发送邮件...");
    }
}


普通工厂:

public class SendFactory {
    // 自定义成员方法实现对象的创建
    public Sender produce(String type) {
        
        if ("mail".equals(type)) {
            return new MailSender();
        }
        if ("sms".equals(type)) {
            return new SmsSender();
        }
        return null;
    }


测试:

public class SendFactoryTest {

    public static void main(String[] args) {

        // 缺点:代码复杂,可读性略差
        // 优点:扩展性和可维护性更强!  尤其是在创建大量对象的前提下
        // 1.声明工厂类类型的引用指向工厂类类型的对象
        SendFactory sf = new SendFactory();

        // 2.调用生产方法来实现对象的创建
        Sender sender = sf.produce("mail");
        
        // 3.使用对象调用方法模拟发生的行为
        sender.send();  //  输出:正在发送邮件...

    }
}

主要缺点 :在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,并且可能出现空指针异常。

  • 2.2.2 多个工厂方法模式:


多个工厂:

public class SendFactory {
    // 自定义成员方法实现对象的创建
    public Sender produce(String type) {
        
        if ("mail".equals(type)) {
            return new MailSender();
        }
        if ("sms".equals(type)) {
            return new SmsSender();
        }
        return null;
    }

    public Sender produceMail() {
        return new MailSender();
    }
    public Sender produceSms() {
        return new SmsSender();
    }
}


测试:

public class SendFactoryTest {

    public static void main(String[] args) {

        // 缺点:代码复杂,可读性略差
        // 优点:扩展性和可维护性更强!  尤其是在创建大量对象的前提下
        // 1.声明工厂类类型的引用指向工厂类类型的对象
        SendFactory sf = new SendFactory();

        // 2.调用生产方法来实现对象的创建
        Sender sender = sf.produceMail;  //多个工厂
        
        // 3.使用对象调用方法模拟发生的行为
        sender.send();  //  输出:正在发送邮件...

    }
}

主要缺点 :在多个工厂方法模式中,为了能够正确创建对象,先需要创建工厂类的对象才能调用工厂类中的生产方法。

  • 2.2.3 静态工厂方法模式:


静态工厂:

public class SendFactory {
    // 自定义成员方法实现对象的创建
    public Sender produce(String type) {
        //System.out.println("随便加一句打印进行测试");
        if ("mail".equals(type)) {
            return new MailSender();
        }
        if ("sms".equals(type)) {
            return new SmsSender();
        }
        return null;
    }

    public static Sender produceMail() {
        return new MailSender();
    }
    public static Sender produceSms() {
        return new SmsSender();
    }
}


测试:


public class SendFactoryTest {

    public static void main(String[] args) {

        // 缺点:代码复杂,可读性略差
        // 优点:扩展性和可维护性更强!  尤其是在创建大量对象的前提下
        // 1.声明工厂类类型的引用指向工厂类类型的对象
        //SendFactory sf = new SendFactory();

        // 2.调用生产方法来实现对象的创建
        //Sender sender = sf.produce("mail");
        //Sender sender = sf.produce("maill");
        //Sender sender = sf.produceMail();
        Sender sender = SendFactory.produceMail();  //静态工厂

        // 3.使用对象调用方法模拟发生的行为
        sender.send();

    }
}

工厂方法模式适合:凡是出现了大量的产品需要创建且具有共同的接口时,可以通过工厂方法模式进行创建。


主要缺点 :

工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序生产新的产品,就必须对工厂类的代码进行修改,这就违背了开闭原则。

2.3 抽象工厂模式

围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。

抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类。

就拿上面的例子来讲,抽象工厂模式对比上面的工厂方法模式区别就是:工厂方法模式是一个工厂类(SendFactory )中有两个生产方法(produceMail()和produceSms())。而抽象工厂就是两个工厂类各自有一个生产方法,然后让两个工厂类实现同一个接口。


适用场景:

  • 客户端不依赖于产品类实例如何被创建、实现等细节。

  • 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码。

  • 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现。


优点:

  • 具体产品在应用层的代码隔离,无需关心创建的细节。

  • 将一个系列的产品统一到一起创建。


总结:

1、简单工厂模式(静态工厂模式):虽然某种程度上不符合设计原则,但实际使用最多。

2、工厂方法模式:不修改已有类的前提上,通过增加新的工厂类实现扩展。

3、抽象工厂模式:不可以增加产品,可以增加产品族。


应用场景:

1、JDk中Calendar的getInstance方法

2、JDBC中的Connection对象的获取

3、Spring中IOC容器创建管理bean对象

4、反射中Class对象的newInstance方法


核心本质:

  • 实例化对象不使用new,用工厂方法替代。

  • 将选择实现类,创建对象统一管理和控制,从而将调用者跟我们的实现类解耦。

2.4 装饰器模式

装饰器模式就是给一个对象动态的增加一些新功能,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。


实际意义:

可以实现一个类功能的扩展。可以动态的增加功能,而且还能动态撤销(继承不行)。

缺点:产生过多相似的对象,不易排错。

2.5 代理模式

  • 代理模式就是找一个代理类替原对象进行一些操作。

  • 如果在使用的时候需要对原有的方法进行改进,可以采用一个代理类调用原有方法,并且对产生的结果进行控制,这种方式就是代理模式。

  • 使用代理模式,可以将功能划分的更加清晰,有助于后期维护。


接口:

public interface Sourceable {
    // 自定义抽象方法
    void method();
}


真实对象:

public class Source implements Sourceable {
    @Override
    public void method() {
        System.out.println("--我是真实对象,也就是被代理的对象(房东)");
    }
}


代理对象:

public class Proxy implements Sourceable {
    private Source source;

    public Proxy() {
        source = new Source();
    }

    @Override
    public void method() {
        source.method();
        System.out.println("我是代理对象!(中介)");
    }
}



访问测试


public class SourceableTest {

    public static void main(String[] args) {

         // 使用代理类实现功能(找中介租房)
        Sourceable sourceable = new Proxy();
        sourceable.method();
    }
}


代理模式和装饰器模式的比较:

  • 装饰器模式通常的做法是将原始对象作为一个参数传给装饰者的构造器,而代理模式通常在一个代理类中创建一个被代理类的对象。
  • 装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。

2.6 模板设计模式

模板方法模式主要指一个抽象类中封装了一个固定流程,流程中的具体步骤可以由不同子类进行不同的实现,通过抽象类让固定的流程产生不同的结果。


实际意义:

  • 将多个子类共有且逻辑基本相同的内容提取出来实现代码复用。
  • 不同的子类实现不同的效果形成多态,有助于后期维护。



版权声明:本文为bier_zm原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。