设计模式的学习笔记

简单工厂模式


1)场景问题


在Java应用开发环境中,要“面向接口编程”。接口的职责就在于“对对象的行为进行封装”;

接口和抽象类的区别
接口是一个特殊的抽象类:二者相比较而言,一般优先选择接口。
在既要定义子类行为,又要为子类提供公共服务的情况下,使用抽象类;



//客户端
public class Client{
    public static void main(String[] args){
        Api api = new Impl();
        api.test1("Lee");
    }
}
//接口类    
interface Api{
    public void test1(String s);
}
//实现类
class ApiImpl implements Api{
    public void test1(String s){
        System.out.println(s);
    }
}

上述实现过程中,我们不但知道了接口,同时还知道了具体的实现就是ApiImpl。而事实上,客户端根本就不应该知道具体的实现类是Impl;
那如何在不具体的使用实现类的情况下完成功能实现呢?

 

2)解决方案


简单工厂模式:提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类。


public class Client{
    public static void main(String[] args){
        Api api = new Factory().CreateApi(1);
        api.test("Lee");
    }
}
class Factory{
    public static Api CreateApi(int condition){
        Api api = null;
        if(condition == 1){
            api = new ApiImplA();
        }else if(condition == 2){
            api = new ApiImplB();
        }
        return api;
    }
}
interface Api{
    public void test(String s);
}

class ApiImplA{
    public void test(String s){
        System.out.println(s+" A");
    }
}

class ApiImplB{
    public void test(String s){
        System.out.println(s+" B");
    }
}


3)模式讲解

       然而简单工厂模式的缺点也很明显,就是一旦增加了新的实现类,我们都必须去改写工厂类。这并不满足六大原则中的开闭原则;那如何实现增加类而不修改类呢?

       一个解决的办法就是使用配置文件,当有了新的实现类,只要在配置文件中配置上新的实现类就好了。在简单工厂的方法里面,可以使用反射,当然也可以使用IOC/DI类实现。
    
这里要提到的两个核心操作就是读取properties文件,获取文件中的键值对;
然后根据键值对和反射机制来动态创建类;

package Factory;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class Factory {
    private Api createApi() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {

        //读取properties文件
        Properties properties = new Properties();
        InputStream inputStream = new BufferedInputStream(new FileInputStream(this.getClass().getResource("").getPath()+"Factory.properties"));
        properties.load(inputStream);
        inputStream.close();
        //利用反射机制来创建配置文件中的类
        return (Api)Class.forName(properties.getProperty("Impl")).newInstance();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        Api api = new Factory().createApi();
        System.out.println(api.test("Lee"));
    }
}
interface Api {
    String test(String s);
}
class ApiImplB implements Api{
    @Override
    public String test(String s) {
        return s+"-B";
    }
}
class ApiImplA implements Api{
    @Override
    public String test(String s) {
        return s+"-A";
    }
}

然后新建一份Factory.properties文件

Impl=Factory.ApiImplB

简单工厂模式的优缺点
    帮助封装
    解耦
    
    增加客户端的复杂度,需要让客户端理解各个参数的具体功能及含义,这会增加客户端的使用难度,也部分暴露了内部实现,这种情况可以选用可配置方式来实现;


    
4)思考


简单工厂的本质思想:选择实现,在不向客户端暴露具体方法的模式下,实现“选择创建对象”

何时选用简单工厂呢?
    如果想要完全封装隔离具体实现,让外部只能通过接口来操作封装体,那么可是选用;
    如果想要把对外开放的接口集中管理和控制,可是选用;


外观模式


1.场景问题:


    当客户端调用某一个的A,B,C等功能模块时,需要了解各个模块的功能及组合关系,就像我们自己组装电脑,想要完成组装,就需要对电脑的各个组件有所了解。而组装电脑还有另一种方式,就是请特定的组装公司对电脑进行组装,自己不去了解组件。这就是“外观模式”(facade)

待解决的问题:
    客户端为了使用模块功能,需要与系统内部的多个模块交互。
    这对于客户端而言,是个麻烦,使得客户端不能简单的使用功能。而且,如果其中的某个模块发生了变化,还可能会引起客户端也要随着变化。
    
    那么如何实现,才能让系统外部的客户端在使用子系统的时候,既能简单的使用这些子系统内部的模块功能,而又不用客户端与系统内部的多个模块交互呢?


    
2.解决方案:


    使用外观模式来解决问题:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
    
    这里需要理解两个词,一个是界面,一个是接口;
    
    1.界面
    界面是相对于类来说的,一个类的public方法,可以看作是这个类的外观。从类的外部来看,类的public方法就是这个类的界面。因为外部看不见类的内部实现,只能看到public方法。
    
    2.接口
    这里提到的接口主要是外部和内部交互的通道,通常是指一些方法,可以是类的方法,也可以是interface的方法。
    
仔细分析上面的问题,客户端想要操作更简单点,那可以根据客户端的需求来定制一个符合客户端要求的方法和接口,然后让客户端来调用这些接口,剩下的事情客户端就不用管它了,这样一来,事情就简单了。而这个特定的类,就被称为外观。

外观模式的主要思想就是:简化调用

与中介者模式的区别,外观模式的调用是单向的,而中介者模式的调用的多个模块之间的。

外观模式结构:

public class Client{
    public static void main(String[] args){
        new Facade().test();
    }
}
class Facade{
    public void test(){
        AModuleApi a = new AModuleImpl();
        a.testA();
        BModuleApi b = new BModuleImpl();
        b.testB();
        CModuleApi c = new CModuleImpl();
        c.testC();
    }
}
interface AModuleApi{
    void testA();
}
class AModuleImpl implements AModuleApi{
    public void testA(){};
}
interface BModuleApi{
    void testB();
}
class BModuleImpl implements BModuleApi{
    public void testB();
}
interface CModuleApi{
    void testB();
}
class CModuleImpl implements CModuleApi{
    public void testB();
}

外观模式的优缺点:
    优点:
        松散耦合;
        简单易用;
        划分访问层次;
    缺点:
        过多的Facade类容易让人迷惑,到底是调用Facade好呢,还是直接调用模块好;

 

适配器模式


1.场景问题:


    如何让新的的接口可以适应原有的类的接口需要呢?
    
解决办法:
    采用一个转接线类,转接线可以将新的接口是配成原有的接口。这个转接线类就是——适配器;


    
2.适配器模式


将一个类的接口转换成客户希望的另一个接口,也就是说,基于原有的接口实现符合当前调用的接口;
    
适配器的思想:转换匹配,复用功能

适配器的结构

public class Client{
    Target target = new Adapter();
    target.request();
}
interface Target{
    void request();
}
//原接口
class Adaptee{
    public void specificRequest(){}
}
//适配后的接口
class Adapter implements Target{
    //原有的接口
    private Adaptee adaptee;
    public Adapter(Adaptee adaptee){this.adaptee = adaptee;}
    //适配
    public void request(){adaptee.specificRequest();}
}

适配器模式的优缺点:
    优点:
        良好的复用性
        更好的扩展性
    缺点:
        适配器过多时,会让系统非常的零乱,不容易整体把控

 

单例模式


1.场景问题:


    读取配置文件时,会使用到配置文件的读取对象。如果系统中多次使用到配置文件对象的内容,那就需要多次创建文件对象,这样就会造成内存浪费。也就是说,对于系统而言,配置文件只需要加载一次就够了,只需要一个实例对象就够了。
    那么问题来了,该如何实现上述的思想呢?


2.解决方案:


单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
    
应用单例模式的思路:


       一个类能够被创建多个实例,问题就在于类的构造方法是公开的,也就是可以让类的外部来通过构造方法创建多个实例。也就是说,把创建实例的方法交给别人,那么就没法去控制外部创建的实例个数。
    
       要想控制一个类只被创建一个实例,那么首要问题就是要把创建实例的权限收回来。让类自身来控制实例的创建,然后将这个创建方法公开就行了。

懒汉式的单例模式:时间换空间;
    每次用到的时候,都需要进行判断,看看是否需要创建。如果一直没有被调用,则可以节约时间。

饿汉式的单例模式:空间换时间;
    不用进行判断,直接创建,节省时间开销;不管是否使用,都会创建;
    
线程安全
    饿汉式的单例模式是线程安全的;因为JVM只会创建一次
    
    饿汉式不是线程安全的。那如何保证饿汉式的线程安全呢?
    1.public static synchronized Singleton getInstance() 在单例全局切入点使用synchronize关键字,但这样会降低整个的访问速度,而且每次都要进行判断
    
    2.双重校验锁

    public Singleton getInstance(){
        private volatile static Singleton instance = null;
        private Singleton(){}
        public static Singleton getInstance(){
            if(instance == null){
                synchronized(Singleton.class){
                    if(instance == null){
                        instance = new Singleton();
                    }
                }
            }
        }
    }


    这种实现方式可是保证线程安全,而且性能上也影响不大;这个相对于第一种方法的改进就是,可以屏蔽掉一部分安全的线程,当不安全成分产生时,再进入同步代码块进行判断;这样可以节省同步时所浪费的时间;
    但是volatile关键字可能会屏蔽掉JVM中一些必要的代码优化,所以运行效率并不高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用“双重校验锁”来实现线程安全的单例,但并不建议大量采用,可以根据实际情况来定;
    
    3.改进饿汉式——实现又可以实现延迟加载,又可以实现线程安全
    思路:采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的饿汉式实现方式,会在类装在的时候就初始化对象,不管你需不需要。
    
    如果有一种方法能够让类装在的时候不去初始化对象,那不就解决问题了吗?
    一种可行的方式就是采用类级内部类。在这个类级内部类里面去创建对象实例,这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而实现延迟加载和线程安全;
    

    public class Singleton{
        private statice class SingletonHolder(){
            private static Singleton singletion = new Singleton();
        }
        private Singleton(){}
        public Singleton getInstance(){
            return SingletonHolder.singletion;
        }
    }


    
单例模式的本质:控制实例数目

单例模式的结构:
1)懒汉式

public class Singleton{
    private static Singleton uniqueInstance = null;
    private Singleton(){}
    public static synchronized Singleton getInstance(){
        if(uniqueInstance == null){uniqueInstance = new Singleton();}
        return uniqueInstance;
    }
    public void singletionOperation(){}
    private String singletionData;
    public String getSingletonData(){return singletionData;}
}


2)饿汉式

public class Singleton{
    private static Singleton uniqueInstance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){return uniqueInstance;}
    public void singletionOperation(){}
    private String singletionData;
    public String getSingletonData(){return uniqueInstance;}
}


3)线程安全的延迟加载单例

public class Singleton{
    private static class SingletonHolder{
        private static Singleton instance = new Singleton();
    }
    private Singleton{}
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

工厂方法模式


场景问题:


    在导出数据时:因为所导出的约定格式不同,需要使用不同的方法。
    
    无论是导出什么样的格式,最后导出的都是一个文件,而且系统并不知道究竟要导出什么样的文件,因此需要有一个统一的接口,来描述系统最后生成的对象,并操作输出的文件。

    //定义导出文件操作接口
    public interface ExportFile{
        //data 内容
        //返回导出是否成功
        public boolean export(String data);
    }

    对于实现该接口的对象而言,它并不知道如何实现。

解决办法:

工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化那一个类,Factory Method使一个类的实例化延迟到其子类。
主要思想:是让父类在不知道具体实现的情况下,完成自身的功能调用;而具体的实现延迟到子类来实现

工厂方法模式,就是我们日常开发中的用到的接口层和接口实现层,不过又有区别。
标准的工厂方法模式的组成除了方法提供者,还应该有方法操作者。也就是说,工厂方法一般是不允许外部客户端直接调用的,而是要通过方法操作者来使用方法。

在工厂方法模式里面,客户端要么使用Creator对象,要么使用Creator创建的对象,一般客户端不直接使用工厂方法。当然也可以把工厂方法暴露给客户端操作,但是一般不这么做。

工厂方法模式的结构

public interface Product{ ... }
public class ConcreteProduct implements Product{ ... }
public abstract class Creator{
    protected abstract Product factoryMethod();
    public void someOperation(){
        Product product = factoryMethod();
    }
}
public class ConcreteCreator extends Creator{
    protected Product factoryMethod(){
        return new ConcreteProduct();
    }
}


工厂方法模式的优缺点:
    优点:
        可以在不知具体的情况下编程
        更容易扩展对象的新版本
        连接平行的类层次
    缺点:
        具体产品对象和工厂方法的耦合性。在工厂方法模式中,产品提供者和产品制造者耦合。


抽象工厂模式:


    所解决的是接口间的关联性
    
抽象工厂模式的结构:
    Abstract Factory:抽象工厂,定义创建一系列产品对象的操作接口;
    Concrete Factory:具体的工厂,实现抽象工厂定义的方法,具体实现一系列产品对象的创建
    Abstract Product:定义一类产品对象的接口
    Concrete Product:具体的产品实现对象,通常在具体工厂里面,会选择具体的产品实现对象,来创建符合抽象工厂定义的方法返回的产品类型的对象
    Client:客户端,主要使用抽象工厂来获取一系列所需要的产品对象,然后面向这些产品对象的接口编程,以实现需要的功能。
    

    public class Client{
        public static void main(String[] args){
            AbstractFactory af = new ConcreteFactory();
            af.createProductA();
            af.createProductB();
        }
    }
    interface AbstractFactory{
        AbstractProductA createProductA();
        AbstractProductA createProductB();
    }
    interface AbstractProductA{
    }
    interface AbstractProductB{
    }
    class ProductA implements AbstractProductA{
    }
    class ProductB implements AbstractProductB{
    }
    class ConcreteFactory implements AbstractFactory{
        public AbstractProductA createProductA(){return new ProductA();}
        public AbstractProductA createProductB(){return new ProductB();}
    }
    


    该模式中的最终产出是产品,而产品的组合是由下一层的组件构成。抽象方法模式可以建立基层组件间的关系,使得产品是最佳组件组合;
    
抽象工厂模式的局限:
    产品的抽象工厂与组件耦合,当组件增加或者减少时,作为产品抽象工厂的接口,就需要增加或者减少方法。

那如何解决呢?
    我们可以将产品抽象工厂不参与组件的组合,而是将组合的操作分发到实现类中去完成,只需要在产品抽象类中获得一个产品就够。接口如下

    public interface AbstractFactory{
        //type 用于指定客户所需要的产品
        public Object createProduct(int type);
    }

抽象工厂模式的优缺点
    优点:
        分离接口和实现
        容易切换产品簇
    缺点:
        类层次复杂


生成器模式

场景问题:
    构建不同的文件的步骤都是一样的。都是一次构建文件头,文件体和文件尾。无论是构建Txt文件还是XML文件,步骤不变,所以应该将步骤提炼出来,形成共同的处理过程。
    如何实现呢?
    
解决方案:
    生成器模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

生成器模式的结构:

public interface Builder{
    public void buildPart();
}
public class ConcreteBuilder implements Builder{
    private Product resultProduct;
    public void buildPart(){
        return resultProduct;
    }
    public void buildPart(){};
}
public interface Product{}
public class Director{
    private Builder builder;
    public Director(Builder builder){this.builder=builder;}
    public void construct(){
        builder.buildPart();
    }
}


生成器的优点
    松散耦合
    更容易的改变产品的内部表示
    更好的复用性
生成器的思想:分离整理构建算法和部件构造
    

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值