设计模式
面向对象思想用在大型软件中有很多局限性,设计模式有助于帮助更好地设计。在面试中,设计模式和面向对象的思想一样,很少会有具体的题目,甚至有时候面试官都不会提设计模式,隐形地让面试者优化代码,考察面试者对设计模式对理解。
本文主要记录了四部分:设计模式简介,再谈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();
这里需要注意两点:
- private:除了Singleton类内部,其他地方无法访问变量instance。
- static:确保是静态类变量,这样可以通过类名来访问,无需实例。
接着,需要定义构造函数,并将其设为private,这样在Singleton之外new的话编译器就会报错,即使是singleton的子类也不行。
private Singleton(){}
最后,定义单例对象的获取方法:
public static Singleton getInstance() { return instance; }
同样需要注意:
-
public:这样外部才能调用;
-
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来创建对象的缺点:
- 在编译时必须确定创建哪个类型的对象
- 参数意义不明确



浙公网安备 33010602011771号