论坛首页 Java企业应用论坛

老程序员学模式

浏览 6975 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (3) :: 隐藏帖 (5)
作者 正文
   发表时间:2009-04-08  
不废话, 学的原因是自己觉得基础不够好, 这次看用的是网上都有的《java与模式》, 在这里记一记笔记, 有空就详细说一说, 增加记忆, 当然, 如果我的总结对大家有好处, 就当是意外收获了, 欢迎交流:

Design Pattern, OO 学习笔记

1. 关于继承和delegation(containment, composition):

继承对封装的破坏: 1. 继承能够访问父类中的protect数据 2. 继承的父类接口暴露给了client程序, 而可能子类并不想让外部类知道这些接口, 这时应该用delegation代替继承
继承与LSP(里氏代换原则):接口语义的一致性(长方形正方形的例子), 判断是否应该用继承的终极标准

2. 关于Interface
1). 相较于多重继承, Interface是实现“mixin”(混合体)的安全模式
2). 接口提供了一种“间接依赖”, 消除不必要的耦合, 从而增强了封装性 (针对抽象编程, 而不要针对实现编程)

3. 关于Immutable和read only object
1). natively thread safe
2). 以资源换安全, 特别是如果创建一个immutable object需要大量的资源
3). 强不变和弱不变, 强不变: 所有可以修改状态的方法都是final或类本身就是final 弱不变: 本身不可变但子类可变

4. Proxy模式
1). 客户端利用抽象层的统一接口
2). Java的利用reflect机制动态生成proxy: 动态生成的proxy object调用InvocationHandler, InvocationHandler 调用实际的服务类。 实现proxy和真正服务类的动态绑定。而植入的功能则在InvocationHandler的invoke方法里实现
3). 关键应用:
- 远程调用,RMI
- Copy On Write
- 实现用户权限检查等
- 访问控制, 智能引用, 同步处理等
4). 个人理解, 实际上proxy可以在对象主体的逻辑之外插件化的加入一些外围的控制, 如果针对一个对象的所有接口, 要实现一些统一的外围控制, 则可以用proxy模式。

5. Factory
1) Simple Factory: 根据输入参数输出产品实例
2) Factory Method: 建立与产品结构一致的工厂结构
3) Abstract Factory: 产品为矩阵式, 一维为产品结构, 二维为产品家族。 如生产电脑, 则CPU, 内存分别为一个产品线,而intel CPU+intel 内存为一个产品家族, 同理, AMD CPU+AMD 内存为另一个产品家族。 abstract factory使得产品在产品家族这个维度上易扩展, 在产品线这个维度上难扩展,这个模式因此适用于产品存在类似的矩阵结构并且一个维度比较稳定,另一个维度需要变化的情况
思考: 如果需要一个产品家族可以组合INTEL CPU+ AMD内存, 该如何做?


   发表时间:2009-04-10   最后修改:2009-04-10
继续, 昨天看模式看到凌晨4点多, 把我这老身子骨累的哟, 人过了30就是不行, 这坛子里没有多少上30还学模式的吧? 呵呵

关于抽象工厂的思考题:
抽象工厂适用的scenario是产品为矩阵式, 一维为产品结构, 二维为产品家族。 如生产电脑, 则CPU, 内存分别为一个产品线,而intel CPU+intel 内存为一个产品家族, 同理, AMD CPU+AMD 内存为另一个产品家族。 abstract factory使得产品在产品家族这个维度上易扩展,在产品结构这个维度上难扩展,这个模式因此适用于产品存在类似的矩阵结构并且一个维度比较稳定,另一个维度需要变化的情况

思考: 如果需要一个产品家族可以组合INTEL CPU+ AMD内存, 该如何做?

呵呵这应该是一个典型的需求变更, 当scenario情况改变时, 我们要想一想, 究竟这种改变是一个one time change, 还是今后经常要有的, 对于这个需求的变化, 我们假定它是将来还可能有类似的情况发生。
从数据的角度理解, 实际上它是把产品在产品家族这个维度上的binding给割裂了, 本来一个产品只能属于一个产品家族, 但现在, 一个产品家族的产品, 可以和另一个家族组合而行成新的产品。
模型的改变带来的是抽象层的改变, 这个时候, 我的解决方案是把产品和产品家族的关系给抽象出来:设置产品家族类, 并且和产品建立1 to *关联。

怎么上图? 想上图把这个问题说清楚。

0 请登录后投票
   发表时间:2009-04-10  
6. Singleton
1) 饥饿与懒汉
2) 登记式: 有一个问题, 书中说父类的实例必须存在才可能有子类的实例, 这个对吗? 根据实验似乎不是如此
3) 单例的状态, 当单例包含状态时, 应该注意: 多JVM和多class loader时的应用
4) 何时不用单例:全程变量, 共享资源的周期 何时用单例: 整个系统只需要一个实例, 多创建就是浪费资源时
0 请登录后投票
   发表时间:2009-04-15  
刚看到还有一种内部类式singleton,

# class Singleton{ 
#     private static class SingletonHolder{ 
#        private static Singleton instance = new Singleton() 
#     } 
#     private Singleton(){} 
#     public static Singleton getInstance(){ 
#         return SingletonHolder.instance; 
#     } 
# }
0 请登录后投票
   发表时间:2009-04-15  
关于double check point在java中不可用的解释: 太强大了, 我承认我没看懂, 哪位高人翻译一下吧, 我把文章转过来:

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

The "Double-Checked Locking is Broken" Declaration

Signed by: David Bacon (IBM Research) Joshua Bloch (Javasoft), Jeff Bogda, Cliff Click (Hotspot JVM project), Paul Haahr, Doug Lea, Tom May, Jan-Willem Maessen, Jeremy Manson, John D. Mitchell (jGuru) Kelvin Nilsen, Bill Pugh, Emin Gun Sirer

Double-Checked Locking is widely cited and used as an efficient method for implementing lazy initialization in a multithreaded environment.

Unfortunately, it will not work reliably in a platform independent way when implemented in Java, without additional synchronization. When implemented in other languages, such as C++, it depends on the memory model of the processor, the reorderings performed by the compiler and the interaction between the compiler and the synchronization library. Since none of these are specified in a language such as C++, little can be said about the situations in which it will work. Explicit memory barriers can be used to make it work in C++, but these barriers are not available in Java.

To first explain the desired behavior, consider the following code:

// Single threaded version
class Foo {
  private Helper helper = null;
  public Helper getHelper() {
    if (helper == null)
        helper = new Helper();
    return helper;
    }
  // other functions and members...
  }

If this code was used in a multithreaded context, many things could go wrong. Most obviously, two or more Helper objects could be allocated. (We'll bring up other problems later). The fix to this is simply to synchronize the getHelper() method:

// Correct multithreaded version
class Foo {
  private Helper helper = null;
  public synchronized Helper getHelper() {
    if (helper == null)
        helper = new Helper();
    return helper;
    }
  // other functions and members...
  }

The code above performs synchronization every time getHelper() is called. The double-checked locking idiom tries to avoid synchronization after the helper is allocated:

// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
  private Helper helper = null;
  public Helper getHelper() {
    if (helper == null)
      synchronized(this) {
        if (helper == null)
          helper = new Helper();
      }   
    return helper;
    }
  // other functions and members...
  }

Unfortunately, that code just does not work in the presence of either optimizing compilers or shared memory multiprocessors.
It doesn't work

There are lots of reasons it doesn't work. The first couple of reasons we'll describe are more obvious. After understanding those, you may be tempted to try to devise a way to "fix" the double-checked locking idiom. Your fixes will not work: there are more subtle reasons why your fix won't work. Understand those reasons, come up with a better fix, and it still won't work, because there are even more subtle reasons.

Lots of very smart people have spent lots of time looking at this. There is no way to make it work without requiring each thread that accesses the helper object to perform synchronization.
The first reason it doesn't work

The most obvious reason it doesn't work it that the writes that initialize the Helper object and the write to the helper field can be done or perceived out of order. Thus, a thread which invokes getHelper() could see a non-null reference to a helper object, but see the default values for fields of the helper object, rather than the values set in the constructor.

If the compiler inlines the call to the constructor, then the writes that initialize the object and the write to the helper field can be freely reordered if the compiler can prove that the constructor cannot throw an exception or perform synchronization.

Even if the compiler does not reorder those writes, on a multiprocessor the processor or the memory system may reorder those writes, as perceived by a thread running on another processor.

Doug Lea has written a more detailed description of compiler-based reorderings.
A test case showing that it doesn't work

Paul Jakubik found an example of a use of double-checked locking that did not work correctly. A slightly cleaned up version of that code is available here.

When run on a system using the Symantec JIT, it doesn't work. In particular, the Symantec JIT compiles

singletons[i].reference = new Singleton();

to the following (note that the Symantec JIT using a handle-based object allocation system).

0206106A   mov         eax,0F97E78h
0206106F   call        01F6B210                  ; allocate space for
                                                 ; Singleton, return result in eax
02061074   mov         dword ptr [ebp],eax       ; EBP is &singletons[i].reference
                                                ; store the unconstructed object here.
02061077   mov         ecx,dword ptr [eax]       ; dereference the handle to
                                                 ; get the raw pointer
02061079   mov         dword ptr [ecx],100h      ; Next 4 lines are
0206107F   mov         dword ptr [ecx+4],200h    ; Singleton's inlined constructor
02061086   mov         dword ptr [ecx+8],400h
0206108D   mov         dword ptr [ecx+0Ch],0F84030h

As you can see, the assignment to singletons[i].reference is performed before the constructor for Singleton is called. This is completely legal under the existing Java memory model, and also legal in C and C++ (since neither of them have a memory model).
A fix that doesn't work

Given the explanation above, a number of people have suggested the following code:

// (Still) Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
  private Helper helper = null;
  public Helper getHelper() {
    if (helper == null) {
      Helper h;
      synchronized(this) {
        h = helper;
        if (h == null)
            synchronized (this) {
              h = new Helper();
            } // release inner synchronization lock
        helper = h;
        }
      }   
    return helper;
    }
  // other functions and members...
  }

This code puts construction of the Helper object inside an inner synchronized block. The intuitive idea here is that there should be a memory barrier at the point where synchronization is released, and that should prevent the reordering of the initialization of the Helper object and the assignment to the field helper.

Unfortunately, that intuition is absolutely wrong. The rules for synchronization don't work that way. The rule for a monitorexit (i.e., releasing synchronization) is that actions before the monitorexit must be performed before the monitor is released. However, there is no rule which says that actions after the monitorexit may not be done before the monitor is released. It is perfectly reasonable and legal for the compiler to move the assignment helper = h; inside the synchronized block, in which case we are back where we were previously. Many processors offer instructions that perform this kind of one-way memory barrier. Changing the semantics to require releasing a lock to be a full memory barrier would have performance penalties.
More fixes that don't work

There is something you can do to force the writer to perform a full bidirectional memory barrier. This is gross, inefficient, and is almost guaranteed not to work once the Java Memory Model is revised. Do not use this. In the interests of science, I've put a description of this technique on a separate page. Do not use it.

However, even with a full memory barrier being performed by the thread that initializes the helper object, it still doesn't work.

The problem is that on some systems, the thread which sees a non-null value for the helper field also needs to perform memory barriers.

Why? Because processors have their own locally cached copies of memory. On some processors, unless the processor performs a cache coherence instruction (e.g., a memory barrier), reads can be performed out of stale locally cached copies, even if other processors used memory barriers to force their writes into global memory.

I've created a separate web page with a discussion of how this can actually happen on an Alpha processor.
Is it worth the trouble?

For most applications, the cost of simply making the getHelper() method synchronized is not high. You should only consider this kind of detailed optimizations if you know that it is causing a substantial overhead for an application.

Very often, more high level cleverness, such as using the builtin mergesort rather than handling exchange sort (see the SPECJVM DB benchmark) will have much more impact.
Making it work for static singletons

If the singleton you are creating is static (i.e., there will only be one Helper created), as opposed to a property of another object (e.g., there will be one Helper for each Foo object, there is a simple and elegant solution.

Just define the singleton as a static field in a separate class. The semantics of Java guarantee that the field will not be initialized until the field is referenced, and that any thread which accesses the field will see all of the writes resulting from initializing that field.

class HelperSingleton {
  static Helper singleton = new Helper();
  }

It will work for 32-bit primitive values

Although the double-checked locking idiom cannot be used for references to objects, it can work for 32-bit primitive values (e.g., int's or float's). Note that it does not work for long's or double's, since unsynchronized reads/writes of 64-bit primitives are not guaranteed to be atomic.

// Correct Double-Checked Locking for 32-bit primitives
class Foo {
  private int cachedHashCode = 0;
  public int hashCode() {
    int h = cachedHashCode;
    if (h == 0)
    synchronized(this) {
      if (cachedHashCode != 0) return cachedHashCode;
      h = computeHashCode();
      cachedHashCode = h;
      }
    return h;
    }
  // other functions and members...
  }

In fact, assuming that the computeHashCode function always returned the same result and had no side effects (i.e., idempotent), you could even get rid of all of the synchronization.

// Lazy initialization 32-bit primitives
// Thread-safe if computeHashCode is idempotent
class Foo {
  private int cachedHashCode = 0;
  public int hashCode() {
    int h = cachedHashCode;
    if (h == 0) {
      h = computeHashCode();
      cachedHashCode = h;
      }
    return h;
    }
  // other functions and members...
  }

Making it work with explicit memory barriers

It is possible to make the double checked locking pattern work if you have explicit memory barrier instructions. For example, if you are programming in C++, you can use the code from Doug Schmidt et al.'s book:

// C++ implementation with explicit memory barriers
// Should work on any platform, including DEC Alphas
// From "Patterns for Concurrent and Distributed Objects",
// by Doug Schmidt
template <class TYPE, class LOCK> TYPE *
Singleton<TYPE, LOCK>::instance (void) {
    // First check
    TYPE* tmp = instance_;
    // Insert the CPU-specific memory barrier instruction
    // to synchronize the cache lines on multi-processor.
    asm ("memoryBarrier");
    if (tmp == 0) {
        // Ensure serialization (guard
        // constructor acquires lock_).
        Guard<LOCK> guard (lock_);
        // Double check.
        tmp = instance_;
        if (tmp == 0) {
                tmp = new TYPE;
                // Insert the CPU-specific memory barrier instruction
                // to synchronize the cache lines on multi-processor.
                asm ("memoryBarrier");
                instance_ = tmp;
        }
    return tmp;
    }

Fixing Double-Checked Locking using Thread Local Storage

Alexander Terekhov (TEREKHOV@de.ibm.com) came up clever suggestion for implementing double checked locking using thread local storage. Each thread keeps a thread local flag to determine whether that thread has done the required synchronization.

  class Foo {
/** If perThreadInstance.get() returns a non-null value, this thread
has done synchronization needed to see initialization
of helper */
         private final ThreadLocal perThreadInstance = new ThreadLocal();
         private Helper helper = null;
         public Helper getHelper() {
             if (perThreadInstance.get() == null) createHelper();
             return helper;
         }
         private final void createHelper() {
             synchronized(this) {
                 if (helper == null)
                     helper = new Helper();
             }
     // Any non-null value would do as the argument here
             perThreadInstance.set(perThreadInstance);
         }
}

The performance of this technique depends quite a bit on which JDK implementation you have. In Sun's 1.2 implementation, ThreadLocal's were very slow. They are significantly faster in 1.3, and are expected to be faster still in 1.4. Doug Lea analyzed the performance of some techniques for implementing lazy initialization.
Under the new Java Memory Model

As of JDK5, there is a new Java Memory Model and Thread specification.
Fixing Double-Checked Locking using Volatile

JDK5 and later extends the semantics for volatile so that the system will not allow a write of a volatile to be reordered with respect to any previous read or write, and a read of a volatile cannot be reordered with respect to any following read or write. See this entry in Jeremy Manson's blog for more details.

With this change, the Double-Checked Locking idiom can be made to work by declaring the helper field to be volatile. This does not work under JDK4 and earlier.

// Works with acquire/release semantics for volatile
// Broken under current semantics for volatile
  class Foo {
        private volatile Helper helper = null;
        public Helper getHelper() {
            if (helper == null) {
                synchronized(this) {
                    if (helper == null)
                        helper = new Helper();
                }
            }
            return helper;
        }
    }

Double-Checked Locking Immutable Objects

If Helper is an immutable object, such that all of the fields of Helper are final, then double-checked locking will work without having to use volatile fields. The idea is that a reference to an immutable object (such as a String or an Integer) should behave in much the same way as an int or float; reading and writing references to immutable objects are atomic.
Descriptions of double-check idiom

    * Reality Check, Douglas C. Schmidt, C++ Report, SIGS, Vol. 8, No. 3, March 1996.
    * Double-Checked Locking: An Optimization Pattern for Efficiently Initializing and Accessing Thread-safe Objects, Douglas Schmidt and Tim Harrison. 3rd annual Pattern Languages of Program Design conference, 1996
    * Lazy instantiation, Philip Bishop and Nigel Warren, JavaWorld Magazine
    * Programming Java threads in the real world, Part 7, Allen Holub, Javaworld Magazine, April 1999.
    * Java 2 Performance and Idiom Guide, Craig Larman and Rhett Guthrie, p100.
    * Java in Practice: Design Styles and Idioms for Effective Java, Nigel Warren and Philip Bishop, p142.
    * Rule 99, The Elements of Java Style, Allan Vermeulen, Scott Ambler, Greg Bumgardner, Eldon Metz, Trvor Misfeldt, Jim Shur, Patrick Thompson, SIGS Reference library
    * Global Variables in Java with the Singleton Pattern, Wiebe de Jong, Gamelan

0 请登录后投票
   发表时间:2009-04-15  
在effective java里的一种新型的singleton:

// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
0 请登录后投票
   发表时间:2009-04-15   最后修改:2009-04-15
singleton就研究到这儿吧, singleton真的是一个很诡异的模式, 看似简单, 但我敢说没有多少人能够深入到双重检查点的研究上去, 从这一点来看, 我觉得singleton要么不用, 要么就用到能了解细微差别的那种程度, 要真正的考量系统的需求, 是否真的是需要单实例, 还有就是衡量好performance, 内部类和枚举式singleton我觉得应该是今后的两种主流的singleton,

这个帖子应该是最深入的:
http://www.iteye.com/topic/211471?page=1
0 请登录后投票
   发表时间:2009-04-15  
singleton的重点是其使用范围,一般都是将其用作工具类,所以如果你只是将其用作构建对象的一种方式,那么真没必要考虑太多,特别是jdk1.5+。您这也算是幻影需求了吧。这是最长见的模式了,在hibernate中就有,好像用MyEclipse自动生成的sessionFactory就是单例,最好还是理论结合实际。
0 请登录后投票
   发表时间:2009-05-13  
Bridge模式:
1. 理解继承的维度: 什么是继承的维度? 就是继承是在哪个方面, 哪个意义上的继承, 通俗的说, 继承是一种分类, 维度, 就是按什么分类. 比如, 人可以分为男人女人, 也可以分为白人黑人黄人, 这就是两种不同的维度。 (维度这个概念是我个人定义的)
2. Bridge结构能够解耦, 从而降低系统的复杂性。 采用Bridge模式, 实际就是用关联代替继承, 从而达到解耦的目的。 这对于需要多维度继承的系统, 增加了灵活性和可维护性
3. 进一步思考, 为什么要继承? 继承是一个什么东西, 继承是OO的基础, 继承有两大特性, 一个, 继承破坏了封装, 因为子类可以任意修改父类的实现,; 另一个, 继承又导致了多态。 从本质上讲, 继承是人类抽象思维分类学上的概念。 那么问题就在这里, 如果继承破坏了封装, 这不是你想看到的, 那么你就不该继承。 同样, 多态意味着变化, 如果你的系统在某一个维度上存在各种变化, 那么就应该用去封装这种变化, 以后一旦有一个变化, 通过添加子类就可以快速的实现, 这就是继承的好处。 所以, 什么时候该继承, 什么时候不该继承, 应该本着开闭原则去把握。 而不是割裂了系统需求仅仅凭自己的直觉和当时编程的方便。
4. 当我们经过考量判定继承确实不适用的时候, 同时, 又必须封装系统中的变化, 这时候bridge模式就是一种很好的选择了。
0 请登录后投票
   发表时间:2009-05-13  
请问一下大家的类图是如何画出来的? 看有些人谈模式的时候能画很漂亮的图, 是用什么工具画好后转化然后截图吗? 请指教
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics