设计模式(我熟知的设计模式都会记录在这里,佛系更新)

目录

  • 1. 工厂模式(Factory)
    • 1.1 简单工厂模式
    • 1.2 工厂方法模式
    • 1.3 抽象工厂模式
  • 2. 单例模式(Singleton)
  • 3. 适配器模式(Adapter)

1. 工厂模式(Factory)

1.1 简单工厂模式

通过专门定义一个类来负责创建其他类的实例,被创建的实例通常具有共同的父类。

简单工厂模式中包含抽象(Product)角色、具体产品(Concrete Product)角色、工厂(Creator)角色。
设计模式(我熟知的设计模式都会记录在这里,佛系更新)_第1张图片

// 抽象产品:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
public interface Fruit {
    void get();
}
// 具体产品:简单工厂模式所创建的具体实例对象。
public class Apple implements Fruit {
    @Override
    public void get() {
        System.out.println("采集苹果");
    }
}

public class Banana implements Fruit{
    @Override
    public void get() {
        System.out.println("采集香蕉");
    }
}
// 工厂类:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
public class FruitFactory {
    public static Fruit getFruit(String name) {
        if ("apple".equalsIgnoreCase(name)) {
            return new Apple();
        } else if ("banana".equalsIgnoreCase(name)) {
            return new Banana();
        } else {
            System.out.println("create class fail");
            return null;
        }
    }
}
public class SimpleFactoryTest {
    public static void main(String[] args) {
        Fruit apple = FruitFactory.getFruit("apple");
        Fruit banana = FruitFactory.getFruit("banana");
        apple.get();
        banana.get();
    }
}

在这个模式中,工厂类是整个模式的关键所在。它包含必要的判断逻辑。能根据外界给定的信息,决定究竟应该创建哪个具体类的对象。用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的。有利于整个软件体系结构的优化。
简单工厂模式的缺点也正体现在其工厂类上,由于工厂类集中了所有实例的创建逻辑,所以“高内聚”方面做的并不好。另外,当系统中的具体产品类不断增多时,可能会出现要求工厂类也要做相应的修改,扩展性并不很好。

1.2 工厂方法模式

定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。

工厂方法模式中包含抽象工厂(Creator)角色、具体工厂( Concrete Creator)角色、抽象(Product)角色、具体产品(Concrete Product)角色。
设计模式(我熟知的设计模式都会记录在这里,佛系更新)_第2张图片

// 抽象工厂:工厂方法模式的核心,任何工厂类都必须实现这个接口。
public interface FruitFactory {
    Fruit getFruit();
}
// 具体工厂:具体工厂类是抽象工厂的一个实现,负责实例化产品对象。
public class AppleFactory implements FruitFactory {
    @Override
    public Fruit getFruit() {
        return new Apple();
    }
}

public class BananaFactory implements FruitFactory {
    @Override
    public Fruit getFruit() {
        return new Banana();
    }
}
// 抽象产品:工厂方法模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
public interface Fruit {
    void get();
}
// 具体产品:工厂方法模式所创建的具体实例对象。
public class Apple implements Fruit {
    @Override
    public void get() {
        System.out.println("采集苹果");
    }
}

public class Banana implements Fruit{
    @Override
    public void get() {
        System.out.println("采集香蕉");
    }
}
public class FactoryMethedTest {
    public static void main(String[] args) {
        FruitFactory appleFactory = new AppleFactory();
        Fruit apple = appleFactory.getFruit();
        apple.get();

        FruitFactory bananaFactory = new BananaFactory();
        Fruit banana = bananaFactory.getFruit();
        banana.get();
    }
}

系统扩展需要添加新的产品对象时,仅仅需要添加一个具体对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好的符合了“开放-封闭”原则。而简单工厂模式在添加新产品对象后不得不修改工厂方法,扩展性不好。
工厂方法模式与简单工厂模式在结构上的不同不是很明显。工厂方法类的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。

1.3 抽象工厂模式

抽象工厂模式是所有形态的工厂模式中最为抽象和最其一般性的。抽象工厂模式可以向客户端提供一个接口,使得客户端在不必指定产品的具体类型的情况下,能够创建多个产品族的产品对象。

抽象工厂模式中包含抽象工厂(Creator)角色、具体工厂( Concrete Creator)角色、抽象(Product)角色、具体产品(Concrete Product)角色。
设计模式(我熟知的设计模式都会记录在这里,佛系更新)_第3张图片

// 抽象工厂:抽象工厂模式的核心,包含对多个产品结构的声明,任何工厂类都必须实现这个接口。
public interface FruitFactory {
    Fruit getApple();
    Fruit getBanana();
}
// 具体工厂:具体工厂类是抽象工厂的一个实现,负责实例化某个产品族中的产品对象。
public class GreenFruitFactory implements FruitFactory {
    @Override
    public Fruit getApple() {
        return new GreenApple();
    }
    @Override
    public Fruit getBanana() {
        return new GreenBanana();
    }
}

public class RedFruitFactory implements FruitFactory {
    @Override
    public Fruit getApple() {
        return new RedApple();
    }
    @Override
    public Fruit getBanana() {
        return new RedBanana();
    }
}
// 抽象产品:抽象模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
public interface Fruit {
    void get();
}

public abstract class Apple implements Fruit {
    public abstract void get();
}

public abstract class Banana implements Fruit{
    public abstract void get();
}
// 具体产品:抽象模式所创建的具体实例对象。
public class RedApple extends Apple {
    public void get() {
        System.out.println("采集红苹果");
    }
}

public class RedBanana extends Banana {
    public void get() {
        System.out.println("采集红香蕉");
    }
}

public class GreenApple extends Apple {
    public void get() {
        System.out.println("采集绿苹果");
    }
}

public class GreenBanana extends Banana {
    @Override
    public void get() {
        System.out.println("采集绿香蕉");
    }
}
public class AbstractFactoryTest {
    public static void main(String[] args) {
        FruitFactory redFruitFactory = new RedFruitFactory();
        FruitFactory greenFruitFactory = new GreenFruitFactory();

        Fruit redApple = redFruitFactory.getApple();
        redApple.get();
        Fruit redBanana = redFruitFactory.getBanana();
        redBanana.get();
        Fruit greenApple = greenFruitFactory.getApple();
        greenApple.get();
        Fruit greenBanana = greenFruitFactory.getBanana();
        greenBanana.get();
    }
}

抽象工厂中方法对应产品结构,具体工厂对应产品族。

2. 单例模式(Singleton)

单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。GoF对单例模式的定义是:保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。

饿汉式

public class Person {

    private static final Person PERSON = new Person();
    
    private Person() {}
    
    public static Person getPerson() {
        return PERSON;
    }
}

懒汉式

public class Person {

    private static Person person;

    private Person() {}

	// 通过判断Person是否为空来创建对象,但线程不安全
    public static Person getPerson1() {
        if (person == null) {
            person = new Person();
        }
        return person;
    }
    
    // 在getPerson1()的基础上加synchronized保证线程同步,但可能遇到线程阻塞的问题
    public static synchronized Person getPerson2() {
        if (person == null) {
            person = new Person();
        }
        return person;
    }
    
	// 对getPerson2()进行优化————双重检查,使内层if的代码只执行一次,即保证线程安全又可以避免线程阻塞
    public static Person getPerson3() {
    	// 对所有线程做判断
        if (person == null) {
            synchronized (Person.class) {
            	// 只对第一个线程做判断
                if (person == null) {
                    person = new Person();
                }
            }
        }
        return person;
    }
}
public class SingletonTest {
    public static void main(String[] args) {
        Person person1 = Person.getPerson();
        Person person2 = Person.getPerson();
        System.out.println(person1==person2);
    }
}

在应用系统开发中,我们常常有以下需求:

  • 在多个线程之间,比如servlet环境,共享同一个资源或者操作同一个对象
  • 在整个程序空间使用全局变量,共享资源
  • 大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。

因为Singleton模式可以保证为一个类只生成唯一的实例对象,所以这些情况,Singleton模式就派上用场了。

3. 适配器模式(Adapter)

适配器模式是构造型模式之一,用于将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作。在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。

适配器模式中包含目标接口(Target)、被适配者(Adaptee)、适配器(Adapter)
目标接口(我喜欢叫它产物):该角色把其他类转换为我们期望的接口
被适配者(我喜欢叫它原材料):原有的接口,也是希望被改变的接口
适配器:将被适配者和目标接口组合到一起的类。

在代码中,适配器通常实现目标接口来规范这个适配器的最终产物,并且重载一个适配器的构造方法(参数为被适配者)作为适配器的“原材料”,然后定义一个全局变量来存放被适配者,最后通过自身的适配方法调用被适配者的方法来实现适配。

适配器的使用场景:
1.系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
2.想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

来看下面的西游记故事。

public interface Bird {
    void eat();
    void fly();
}
public interface Monkey {
    void eat();
    void jump();
}
public class Eagle implements Bird {
    public void eat() {System.out.println("老鹰吃虫子");}
    public void fly() {System.out.println("老鹰翱翔");}
}
public class SunWukong implements Monkey {
    public void eat() {System.out.println("孙悟空吃桃子");}
    public void jump() {System.out.println("孙悟空一个筋斗十万八千里");}
}
public class Monkey2BirdAdapter implements Bird {

    private Monkey monkey;

    Monkey2BirdAdapter(Monkey monkey) {
        this.monkey = monkey;
    }

    public void eat() {
        monkey.eat();
    }

    public void fly() {
        monkey.jump();
    }
}
public class AdapterTest {

    public static void main(String[] args) {

        Bird eagle = new Eagle();
        Monkey swk = new SunWukong();

        Bird sunEagle = new Monkey2BirdAdapter(swk);

        System.out.println("老鹰还是老鹰:");
        eagle.eat();		// 老鹰吃虫子
        eagle.fly();		// 老鹰翱翔

        System.out.println("孙悟空还是孙悟空:");
        swk.eat();			// 孙悟空吃桃子
        swk.jump();			// 孙悟空一个筋斗十万八千里

        System.out.println("但是老鹰可以是孙悟空变的,这个假鸟以fly的方式飞翔,实际上他还是在翻跟头):");
        sunEagle.eat();		// 孙悟空吃桃子
        sunEagle.fly();		// 孙悟空一个筋斗十万八千里
    }
}

在上面的代码中,Monkey猴类就是被适配者,他可以jump跳,我们希望这个猴子可以像Bird鸟类一样可以fly,这时Bird就是目标接口,我们可以通过Monkey2BirdAdapter适配器实现Bird接口来把Monkey变成Bird,即在fly方法(适配方法)中调用jump方法(当然也可以新写一个适配方法调用jump方法),在使用上,只需要调用适配器的fly方法即可。

下面我们再来看一个JDK源码中的适配器模式案例(以下所有源码来自JDK 8)。

我们知道创建线程的方式有两种,继承Thread类或实现Runable接口,Thread的子类创建的对象就是线程对象,而Runable的实现类创建的对象仅仅是Thread的target,其中的run方法和Thread中的run方法一样是线程执行体,但实际的线程对象依然是Thread实例,如果希望用Thread执行target中的run方法,可用Thread类的重载构造方法,即

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

这里先记住这个构造方法,下面正文开始,现在我们需要一个类似Thread或Runable的run方法一样的线程执行体,但这个执行体可以抛异常和返回值(Thread或Runable的run方法是无返回值和无法抛出异常的),于是在java 5开始,引入了一个接口——Callable,该接口提供了一个叫call的方法,如下

public interface Callable<V> {
    V call() throws Exception;
}

如果我们需要一个可以抛异常和有返回值的线程执行体,只需要继承Callable并重写call方法(call方法的方法体即为线程执行体)即可。

但现在遇到了问题,Callable不是Runable的子接口,所以不能直接作为Thread的target,而且call()方法还有返回值,如何获取返回值呢?
这时java 5提供了Future接口来管理Callable接口里call方法的返回值。

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();
    
    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

并为Future接口提供了一个FutureTask实现类(以下源码省略了与本讲解无关的代码),其中通过get方法即可获取Callable的call()方法的返回值(即全局变量outcome)。

public class FutureTask<V> implements RunnableFuture<V> {

	// 用于存放构造器中的Callable
    private Callable<V> callable;
    // 用于存放call()方法的返回值
    private Object outcome;

	// 该方法在get()中被调用,返回outcome即Callable的call()方法的返回值
    private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }
    
    // 获取Callable
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;
    }
    
    // 获取Callable的call()方法的返回值
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
    
    // 该方法在run()中被调用,将Callable的call()的返回值存入outcome
    protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
    
    public void run() {
		// run方法这里先暂时省略
    }
}

FutureTask实现了RunnableFuture接口,该接口如下

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

可见FutureTask实现了Runnable和Future两个接口,Future规定了FutureTask必须要实现的扩展方法,并实现了Runnable的run方法,这时回头看FutureTask重写的run方法,

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        runner = null;
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

这个run方法(其实就是适配方法)的主要作用就是将Callable的call方法的返回值(通过set方法)存入全局变量outcome中,再通过get方法从outcome中取值。

下面上测试代码(可在java 5以上直接复制粘贴运行)

public class CallableImpl implements Callable<Integer> {

    private int i = 0;
    @Override
    public Integer call() throws Exception {
        for (; i < 100; i++) {
            System.out.println("call方法打印:" + Thread.currentThread().getName() + ",i的值:" + i);
        }
        return i;
    }

    public static void main(String[] args) {
        Thread.currentThread().setName("main方法的名字");
        // 通过FutureTask的有参构造创建实例,这里传入被适配者CallableImpl,用于内部处理CallableImpl
        FutureTask<Integer> future = new FutureTask<>(new CallableImpl());
        for (int i = 0; i < 100; i++) {
            if (i == 20) {
            	// FutureTask因为实现了Runnable接口而可以通过Thread的构造方法创建线程实例
                Thread c1 = new Thread(future, "FutureTask创建的线程");
                c1.start();
                System.out.println(c1.getName());
            }
        }
        try {
        	// 通过get方法获取值
            System.out.println("future的值:" + future.get()); // 100
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

在上面的测试代码中,我们希望可以通过Thread的有参构造方法(参数是Runnable)创建Callable(被适配者)的线程实例,又希望获取Callable线程执行体返回的值,这时就可以用适配器FutureTask将Callable(被适配者&原材料)转为Runnable(目标接口&产物),它因为实现了Runnable接口而可以通过Thread的有参构造方法创建线程实例,而且可以通过get方法获取Callable线程执行体返回的值(run方法负责适配器对Callable的读取处理,get方法是我们直接可调用的读取方法)。

适配器的优点
1.将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
2.增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
3.灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
适配器的缺点
1.对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
2.适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;

你可能感兴趣的:(笔记,设计模式,java,面试)