Java面试16|设计模式

1、单例模式:

确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式有以下几个要素:

  • 私有的构造方法
  • 指向自己实例的私有静态引用
  • 以自己实例为返回值的静态的公有的方法

        单例模式根据实例化对象时机的不同分为两种:一种是饿汉式单例,一种是懒汉式单例。饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用;而懒汉式在调用取得实例方法的时候才会实例化对象。

饿汉式:

1
2
3
4
5
6
7
8
9
10
11
public  class  Singleton_Simple { 
       
     private  static  final  Singleton_Simple simple =  new  Singleton_Simple(); 
       
     private  Singleton_Simple(){} 
       
     public  static  Singleton_Simple getInstance(){ 
         return  simple; 
    
   
}

懒汉式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//双锁机制
class  Singleton {
     private  volatile  static  Singleton singleton;
     public  static  Singleton getInstance() {
         if  (singleton ==  null ) {
             synchronized  (Singleton. class ) {  // 由于每次调用都需要进行同步,所以可以在前面进行判断即可提高效率,同时注意使用的是Singleton.class的类锁
                 if  (singleton ==  null ) {
                     singleton =  new  Singleton();
                 }
             }
         }
         return  singleton;
     }
}

为了提高效率。我们可以用double check机制,现在来看两个问题:

(1)为何用double check的检测机制?

由于两个线程都可以通过第一重的判断 ,进入后,由于存在锁机制,所以会有一个线程进入同步块和第二重判断,而另外的一个线程在同步块处阻塞。

当第一个线程退出同步代码块后,第二个进入,没有经过第二重判断,保证了单例。所以这里必须要使用双重检查锁定。

其实没有第一重的判断,我们也可以实现单例,只是为了考虑性能问题。

(2)为何要对实例变量加volatile关键字?

java内存模型(jmm)并不限制处理器重排序,在执行instance=new Singleton()时,并不是原子语句,实际是包括了下面三大步骤: 

1.为对象分配内存

2.初始化实例对象

3.把引用instance指向分配的内存空间

ps:对象创建可参看《深入理解Java虚拟机》第44页

这个三个步骤并不能保证按序执行,处理器会进行指令重排序优化,存在这样的情况:

优化重排后执行顺序为:1,3,2, 这样在线程1执行到3时,instance已经不为null了,线程2此时判断instance!=null,则直接返回instance引用,但现在实例对象还没有初始化完毕,此时线程2使用instance可能会造成程序崩溃。

现在要解决的问题就是怎样限制处理器进行指令优化重排。

volatile的作用:

(1)volatile变量不会以被缓存到寄存器或者对其它处理器不可见的地方,因此在读取volatile类型的变量时总是返回最新写入的值。 

(2)volatile关键字能够通过提供内存屏障,来保证某些指令顺序处理器不能够优化重排,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

 

JVM会对分配内存空间的动作进行同步处理。

(1)可能实际上采用CAS(Compare And Set)配上失败重试的方式保证更新操作的原子性

(2)把内存分配的动作按照线程划分在不同的空间之中进行。就是每个线程在Java堆中预先分配一小块内存,

参考Java虚拟机第45页

 

其实还有种写法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public  class  Singleton {
     
       private  static  class  SingletonClassInstance {  // 私有静态内部类
         // 可能是final解决了并发的问题,基本类型声明就可,但是对象类型时,这个对象不能有状态,
         // 如果有状态,则一定要声明为final,例如String类就是一个不可变类
         private  static  final  Singleton instance =  new  Singleton();
       }
 
       public  static  Singleton getInstance() {
         return  SingletonClassInstance.instance;  // 只在这里调用了静态内部类,私有属性让他人无法使用这个类型
       }
 
       private  Singleton() {  }
}

由于Singletom没有static属性且SingletonClassInstance是私有静态内部类,不会被其他类调用,所以不会被提前初始化,实现了懒汉式的构造实例。

static语义也要求不会有多个实例存在。并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。
  
虚拟机会保证一个类的<clinit>()方法在多线程环境中被 正确地加锁、同步。如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。

 

重点要知道为什么静态内部类能保证单例:因为只有一个线程去初始化这个类,在初始化后static final就是一个常量了,当然线程安全了。 

 

其在实际中有重要的应用,如:

1、单例模式在Log4j中的应用。将日志输出到一个文件中必须使用单例模式,否则会覆盖文件的原有内容
2、数据库连接池中的应用

 

2、迭代器模式:

提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface  Iterable{
     public  Iterator iterator();
}
 
interface  Aggregate  extends  Iterable{
     public  void  add(Object obj);
     public  void  remove(Object obj);
     public  Iterator iterator();
}
 
class  ConcreteAggregate  implements  Aggregate {
     private  List list =  new  ArrayList();
 
     public  void  add(Object obj) {
         list.add(obj);
     }
 
     public  Iterator iterator() {
         return  new  ConcreteIterator(list);
     }
 
     public  void  remove(Object obj) {
         list.remove(obj);
     }
}

调用iterator()方法后返回一个Iterator迭代器,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface  Iterator {
     public  Object next();
     public  boolean  hasNext();
}
 
class  ConcreteIterator  implements  Iterator {
     private  List list =  new  ArrayList();
     private  int  cursor =  0 ;
 
     public  ConcreteIterator(List list) {
         this .list = list;
     }
 
     public  boolean  hasNext() {
         if  (cursor == list.size()) {
             return  false ;
         }
         return  true ;
     }
 
     public  Object next() {
         Object obj =  null ;
         if  ( this .hasNext()) {
             obj =  this .list.get(cursor++);
         }
         return  obj;
     }
}

测试一下:

1
2
3
4
5
6
7
8
9
Aggregate ag =  new  ConcreteAggregate();
ag.add( "小明" );
ag.add( "小红" );
ag.add( "小刚" );
Iterator it = ag.iterator();
while  (it.hasNext()) {
     String str = (String) it.next();
     System.out.println(str);
}

 

3、组合设计模式:

蜜蜂是昆虫:继承实现

蜜蜂有向前移动后攻击人的行为:组合实现

昆虫:

1
2
3
4
5
6
7
8
9
10
11
12
class  Insect {
     private  String name;
     public  Insect(String name) {
         this .name = name;
     }
     public  String getName() {
         return  name;
     }
     public  void  setName(String name) {
         this .name = name;
     }
}

攻击行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface  Attack {
     public  void  move();
     public  void  attack();
}
 
class  AttackImpl  implements  Attack {
     private  String move;
     private  String attack;
  
     public  Attack Impl(String move, String attack) {
         this .move = move;
         this .attack = attack;
     }
     @Override
     public  void  move() {
         System.out.println(move);
     }
     @Override
     public  void  attack() {
         move();
         System.out.println(attack);
     }
}

蜜蜂是昆虫,有向前一步攻击人的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class  Bee  extends  Insect  implements  Attack {
     private  Attack attack;
  
     public  Bee(String name, Attack attack) {
         super (name);
         this .attack = attack;
     }
  
     public  void  move() {
         attack.move();
     }
  
     public  void  attack() {
         attack.attack();
     }
}

由于继承机制太过依赖于父类的实现细节,如果父类变动,则子类也会跟着变动,这是糟糕的。 

 

4、策略设计模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// Different types of function objects:
interface  Combiner<T> {
     T combine(T x, T y);
}
 
 
public  class  Functional {
     // Calls the Combiner object on each element to combine
     // it with a running result, which is finally returned:
     public  static  <T> T reduce(Iterable<T> seq, Combiner<T> combiner) {
         Iterator<T> it = seq.iterator();
         if  (it.hasNext()) {
             T result = it.next();
             while  (it.hasNext()){
                 result = combiner.combine(result, it.next());
             }
             return  result;
         }
         // If seq is the empty list:
         return  null // Or throw exception
     }
 
 
     // To use the above generic methods, we need to create
     // function objects to adapt to our particular needs:
     static  class  IntegerAdder  implements  Combiner<Integer> {
         public  Integer combine(Integer x, Integer y) {
             return  x + y;
         }
     }
 
     static  class  IntegerSubtracter  implements  Combiner<Integer> {
         public  Integer combine(Integer x, Integer y) {
             return  x - y;
         }
     }
 
     static  class  BigIntegerAdder  implements  Combiner<BigInteger> {
         public  BigInteger combine(BigInteger x, BigInteger y) {
             return  x.add(y);
         }
     }
 
 
     public  static  void  main(String[] args) {
         
         // Generics, varargs & boxing working together:
         List<Integer> li = Arrays.asList( 1 2 3 4 5 6 7 );
         
         Integer result = reduce(li,  new  IntegerAdder());
         print(result);  // 28
 
         result = reduce(li,  new  IntegerSubtracter());
         print(result);
 
 
         // Use the prime-generation facility of BigInteger:
         List<BigInteger> lbi =  new  ArrayList<BigInteger>();
         BigInteger bi = BigInteger.valueOf( 11 );
         for  ( int  i =  0 ; i <  11 ; i++) {
             lbi.add(bi);
             bi = bi.nextProbablePrime();
         }
         print(lbi);
 
         BigInteger rbi = reduce(lbi,  new  BigIntegerAdder());
         print(rbi);
         
     }
}

如上例子参考了Java编程思想关于泛型实现策略模式的例子。  

 

5、原型设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class  bottle  implements  Cloneable {
     public  Wine wine;
 
     public  bottle(Wine wn) {
         this .wine = wn;
     }
 
     // 覆写clone()方法
     protected  Object clone()  throws  CloneNotSupportedException {
         bottle newBottle = (bottle)  super .clone();
         newBottle.wine = (Wine) wine.clone();
         return  newBottle;
     }
}
 
class  Wine  implements  Cloneable {
     int  degree;
     String name= "法国白兰地" // 字符串虽然是引用类型,但是新对象中修改时并不会影响到原对象值
 
     public  int  getDegree() {
         return  degree;
     }
 
     public  void  setDegree( int  degree) {
         this .degree = degree;
     }
 
     // 覆写clone()方法
     protected  Object clone()  throws  CloneNotSupportedException {
         return  super .clone();
     }
}

  

 

 

下面来总结一下各个设计模式在Java中的具体应用。

结构型模式 

单例模式(Single Pattern)

1、Log4j中将日志输出到一个文件中必须使用单例模式,否则会覆盖文件的原有内容

2、数据库连接池中的应用

工厂模式(Factory)

Spring工厂模式:通过XML文件或Java注解来表示Bean之间的依赖关系,很少直接new一个类进行代码编写

建造者模式(Builder)

Struts2中创建容器(Container)对象

原型模式(Protype)

Java的克隆clone()

 结构型模式 

适配器模式(Adapter)

1、在Java的I/O类库中,StringReader将一个String类适配到Reader接口,InputStreamReader将InputStream适配到Reader类。

2、在Spring中的AOP中,由于Advisor需要的是MethodInterceptor对象,所以每一个Advisor中的Advice都要配成对应的MethodInterceptor对象。

桥接模式(Bridge)

 

装饰模式(Decorator)

1、Collections.synchronizedList例子也是一个装饰器模式

2、装饰器在Junit中的应用。TestDecorator是Test的装饰类,其TestDecorator有一些扩展功能的子类。如RepeatedTest类,TestSetup类等

3、一般情况下,需要使用FileReader和StringReader,如果要增加缓存功能的类有很多,那么子类也就需要很多,所以Java就使用了装饰模式,BufferedReader就是这样的装饰类。其实Java I/O 库中的所有输入流、输出流的类都采用了装饰器模式,它们可以无限次地进行装饰转换,转换的目的就是得到自己想要的数据类型的流对象

 

优点:动态扩展类功能属性,而无需通过继承层次结构实现,更方便灵活给类添加职责

组合模式(Composite)

 

外观模式(Façade)

1、在Spring中已经提供了很好的封装,在org.springframework.jdbc.support包下提供的JdbcUtils类就是这样的工具类

2、在Hibernate的配置工作中,有一个Configuration类,负责管理运行时需要的一些信息

享元模式(Flyweight)

1、数据据连接池是享元模式的重要应用

2、在Java中,对于基本类型和字符串类型的实现就采用了享元模式

代理模式(Proxy)

1、在Spring中已经提供了很好的封装,在org.springframework.jdbc.support包下提供的JdbcUtils类就是这样的工具类

2、在Hibernate的配置工作中,有一个Configuration类,负责管理运行时需要的一些信息

行为型模式 

模版方法模式(Template Method)

1、在Junit中的TestCase类中

2、在MVC框架的HttpServlet中

3、Spring采用开放式的处理方式,在使用JpaTemplate时,只需要处理具体的SQL语句即可,而创建连接、关闭连接等方法都不需要编写代码,因为不管理是哪种持久层的操作应运,其创建和关闭连接都是一致的,按照相同的顺序执行,这就是模板方法模式的应用

命令模式(Command )

 

命令模式在Struts2中的应用。其中action就是命令模式中命令接口,ActionInvocation就是命令模式中命令的调用者

迭代器模式(Iterator )

java中的Collection、List、Set、Map等,这些集合都有自己的迭代器。

观察者模式(Oberver Pattern)

 

中介者模式(Mediator)

 

备忘录模式(Memento)

 

解释器模式(Interpreter)

 

状态模式(State)

 

职责链模式(Chain of Responsibility)

责任链在Struts2中的拦截器上有重要应用

策略模式(Strategy)

1、策略模式允许在程序执行时选择不同的算法.比如在排序时,传入不同的比较器(Comparator),就采用不同的算法

2、Spring的Resource实现思想是典型的策略模式的应用

访问者模式(Visitor)

 1、javac中关于语法树各个节点的语义分析就使用访问者模式来实现的

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值