设计模式

面向对象思想用在大型软件中有很多局限性,设计模式有助于帮助更好地设计。在面试中,设计模式和面向对象的思想一样,很少会有具体的题目,甚至有时候面试官都不会提设计模式,隐形地让面试者优化代码,考察面试者对设计模式对理解。

本文主要记录了四部分:设计模式简介,再谈singleton,变继承关系为组合关系,如何创建对象。

一、设计模式提出:博士论文、四人帮的书

设计:对现实事物进行建模  vs 语言限制(例如visitor pattern)

更多模式:并发模式,架构模式等等

二、Singleton模式(单例模式)

singleton设计模式确保一个类全局最多只能有一个对象,用于(1)构造缓慢的对象,当需要使用时就用这个已经构造好的对象,(2)需要统一管理的资源(比如数据库的连接池)

缺点:有很多全局状态,线程安全性难以保证

由于singleton模式容易实现,常在面试中考到。下面是五种实现单例模式的方式,此部分参考博客https://www.cnblogs.com/pkufork/p/java_singleton.html

1、最简单的方法是在类加载的时候初始化这个单独的实例。 

首先,定义单例类(没有特别的,就是起个名字):

public class Singleton {

然后,需要定义类变量将单例对象保存,

private static Singleton instance = new Singleton();

这里需要注意两点:

  1. private:除了Singleton类内部,其他地方无法访问变量instance。
  2. static:确保是静态类变量,这样可以通过类名来访问,无需实例。

接着,需要定义构造函数,并将其设为private,这样在Singleton之外new的话编译器就会报错,即使是singleton的子类也不行。

private Singleton(){}

最后,定义单例对象的获取方法:

public static Singleton getInstance() {
    return instance;
}

同样需要注意:

  1. public:这样外部才能调用;

  2. static:这样外部才能通过类名来访问,否则获取不到单例对象;

2、使用懒加载。在方法一的基础上修改代码

首先,静态类变量应该初始化为NULL:

private static Singleton instance = null;

 

其次,getInstance需要在第一次调用时承担生成实例的任务:

public static Singleton getInstance() {
    if(instance == null) {
        instance = new Singleton();
    }
    return instance;
}

3、考虑多线程(方法二仅适用于单线程的情况,在多线程时仍然可能生成多个实例,需要用同步来避免多线程的问题。)(为了在多线程环境下还能保证仅生成一个实例,需要加上一个同步锁)

修改如下:

public static synchronized Singleton getInstance() {
    if(instance == null) {
        instance = new Singleton();
    }
    return instance;
}

4、考虑性能的问题。synchronized关键字修饰getInstance()方法,会导致所有调用getInstance()方法的线程都要竞争同一把锁,即使在单例对象已经生成好的情况下。这里使用double check的方式。另外,还需要注意instance在初始化过程中,可能已经不为NULL,这样有可能会导致程序崩溃,因此这里加了个临时变量t,确保初始化完成之后再赋值给instance。

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

5、更加简化的方法。基于静态内部类的特性,我们可以利用它的懒加载机制简化实现:

public static Singleton getInstance(){
    return NestedClass.instance;
}

private static class NestedClass{
    private static Singleton instance = new Singleton();
}

内部类NestedClass

利用Java类的静态变量只有一份这一特性,大大简化代码。

这一写法的缺点,程序初始化时必须创建这一静态变量,不然又有多线程的问题。

 

很多问题可以利用框架解决,dependency injection

三、变继承关系为组合关系

 继承关系用于描述is-a关系。

利用继承关系来实现复用:protected, 给派生类用,让派生来来实现。

用设计模式来实现复用更好。

举例:如果Employee升级成了Manager?

is-a关系时:employee1 = new Manager();

组合关系时:

其中Employee的Role是一个接口,Engineer和Manager是实现了role接口的两个类。

private Role role;
public void doWork(){
   role.doWork();
}

 问题二(Decorator Pattern):有接口Runnable如下,如何实现LoggingRunnable, TransactionalRunnable,.....

interface Runnable {
   void run();
}

最初的CodingTask为:

public class CodingTask implements Runnable{
    @Override
    public void run() {
        System.out.println("Writing code.");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

 写两个虚类 LoggingRunnable 和 TransactionalRunnable 用给 CodingTask继承

public abstract class LoggingRunnable implements Runnable {
    protected abstract void doRun();
    @Override
    public void run() {
        long startTime = System.currentTimeMillis();
        System.out.println("Task started at " + startTime);
        doRun();
        System.out.println("Task finished. Elapsed time: "
            + (System.currentTimeMillis() - startTime));
    }
}
public abstract class TransactionalRunnable implements Runnable {
    protected abstract void doRun();
    @Override
    public void run() {
        boolean shouldRollback = false;
        try {
            beginTransaction();
            doRun();
        } catch(Exception e) {
            shouldRollback = true;
            throw e;
        } finally {
            if(shouldRollback) {
                rollback();
            } else {
                commit();
            }
        }
    }
    private void commit(){
        System.out.println("commit");
    }
    private void rollback(){
        System.out.println("rollback");
    }
    private void beginTransaction() {
        System.out.println("beginTransaction");
    }
}

将CodingTask改为

public class CodingTask extends LoggingRunnable {
   @Override
   protected void doRun() {
    System.out.println("Writing code.");
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    }
}    

 

但是Java不允许一个类继承两个类,(即使像C++允许这种继承,也会造成混乱)

解决

public class CodingTask implements Runnable {
    @Override
    public void run() {
        System.out.println("Writing code.");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
public class LoggingRunnable implements Runnable {

    private final Runnable innerRunnable;

    public LoggingRunnable(Runnable innerRunnable) {
        this.innerRunnable = innerRunnable;
    }

    @Override
    public void run() {
        long startTime = System.currentTimeMillis();
        System.out.println("Task started at " + startTime);
        innerRunnable.run();
        System.out.println("Task finished. Elapsed time: "
            + (System.currentTimeMillis() - startTime));
    }
}
public class TransactionalRunnable implements Runnable {
    private final Runnable innerRunnable;

    public TransactionalRunnable(Runnable innerRunnable) {
        this.innerRunnable = innerRunnable;
    }

    @Override
    public void run() {
        boolean shouldRollback = false;
        try {
            beginTransaction();
            innerRunnable.run();
        } catch(Exception e) {
            shouldRollback = true;
            throw e;
        } finally {
            if(shouldRollback) {
                rollback();
            } else {
                commit();
            }
        }
    }

    private void commit(){
        System.out.println("commit");
    }
    private void rollback(){
        System.out.println("rollback");
    }
    private void beginTransaction() {
        System.out.println("beginTransaction");
    }
}
public class Tester {
    public static void main(String[] args) {
        new CodingTask().run();
        new LoggingRunnable(new CodingTask()).run();
        new LoggingRunnable(
                new TransactionalRunnable(
                        new CodingTask())).run();
    }
}

四、如何创建对象

使用new来创建对象的缺点:

  • 在编译时必须确定创建哪个类型的对象
  • 参数意义不明确

 

posted @ 2019-03-18 22:48  起点菜鸟  阅读(170)  评论(0)    收藏  举报