Showing posts with label Design Patterns. Show all posts
Showing posts with label Design Patterns. Show all posts

Tuesday, April 16, 2019

Design Pattern Interview Summary



Design Pattern 设计 - 请看应该选择那种模式
讨论如下设计题Task
  • Implement a type "Robot" with the following requirements
    • Robot must support pluggable chips that allow him to do different operations on numeric arrays
    • Only one chip can be plugged at a time and robot can't function without a chip
    • Chips can be swapped at runtime, since you have only one robot instance (robots are expensive)
  • Robot should have only two functions/methods
    • A function that executes current chip's logic and returns result, it should accept an array and return whatever current chip is returning
    • A function that installs a chip into a robot, it should accept a chip and should not return anything
  • Implement two chips
    • "Chip of Sorts" — sorts an array in the order and can have an option to specify ascending or descending type of sorting. (Sorting algorythm is not important, use standard one from any library you want.)
    • "Total Chip" — finds a total sum of all elements of an array
  • Add a property to your robot that represents total number of different chips installed through robot's lifetime. It should only count unique chips being installed.
  • Write one simple test function that tests logic from 4, you don't have to use any Unit Testing framework, just a simple function would be fine
  • It's up to you how do design your robot and types. Pay attention to make design simple and intuitive.
Bonus
There is no right or wrong solution as long as code works, but we give bonus points for a plain-text explanation on why particular design decisions were made and what are the tradeoffs. Put it right into the code in a form of a comment at the beginning of the main file.

Eviction policy - Design Pattern



关于cache的 eviction policy设计
TODO: https://github.com/apache/ignite/tree/master/modules/core/src/main/java/org/apache/ignite/cache/eviction
https://www.1point3acres.com/bbs/forum.php?mod=viewthread&tid=316982

我觉得可能strategy看起来更好些,考虑到是个behavioral pattern,不同algorithm 重写同一个方法, 但是需要考虑的重点应该变成了如何使得这个重写的方法尽可能够都使用公共的数据存储结构。最坏不做抽象的情况,就是数据结构保存在当前类中,就变成了类似于factory模式的模式了

我刚搜了圈,发现 apache ignite 有类似的抽象的例子
https://ignite.apache.org/releas ... EvictionPolicy.html,不过好像就是把evictionPloicy抽象成abstract class了,然后数据结构也是在子类中定义的。就如同你说的,不一样的policy的数据结构是不一样的,强行去abstract common 数据结构会有点得不偿失,所以用个工厂模式可能会比较好懂,代码也比较直观

就是设计一个cache的eviction policy,你可以选择是LRU, 还是LFU,还是其他eviction policy. 选择哪个,就按照哪个eviction policy执行。
大家认为这种情况,用哪个pattern比较好?


首先回顾一下LeetCode的LRUCache这道题目所设计的接口:
  1. class LRUCache {
  2. public:
  3.   // Get the value (will always be positive) of the key if the key exists in the
  4.   // cache, otherwise return -1.
  5.   int get(int key);

  6.   // Set or insert the value if the key is not already present. When the cache
  7.   // reached its capacity, it should invalidate the least recently used item
  8.   // before inserting a new item.
  9.   void put(int key, int value);
  10. };
复制代码


当然这个接口非常简陋,至少有两个问题:

(1) Cache和eviction policy(LRU)的逻辑是合并在一起的

这也正是我们这里OOD设计所要解决的问题:将eviction policy解耦(decouple)出来,使得其可以作为一个参数(或者模板参数)被传入Cache类,那么得到的Cache对象就会使用对应的policy。

(2) 没有触及cache设计的一个重要部分:value的生存期的管理

如果value是占用较多内存空间的大对象(而不是LeetCode题中仅占用4个字节的栈区int),或者是C/C++写的代码,一般是由cache管理生存期。
也就是说当evict一个value时,其占用内存应该立刻被回收,而这时所有对该value对象的引用应当立刻失效 —— 在这种情况下要避免代码出bug(非法内存访问),必须由cache对value进行引用计数,且返回值使用"counted reference"(类似于C++ shared pointer)。
不过如果返回的是栈区对象/对象的拷贝/带GC语言下(例如Java)的对象引用,那么不需要考虑引用计数问题,但是这时cache就无法保证内存的使用量被控制在规定范围内。

第(2)点其实是为了说一下eviction policy设计时关于引用计数的接口 —— 下面简化的代码中没有实现这一接口,但是需要了解一下,因为很多实际系统中是有相关内容的,例如PostgreSQL:https://github.com/postgres/postgres/blob/master/src/backend/storage/buffer/bufmgr.c#L139

现在回到(1),下面贴的代码主要包含了:
(a) EvictionPolicy的抽象类
(b) 继承了EvictionPolicy的LRU/LFU实现
(c) 一个EvictionPolicy工厂,使得可以比较容易地添加新的Policy实现
(c) 使用EvictionPolicy实现的Cache类


  1. // Eviction policy的抽象
  2. template <typename Entry>
  3. class EvictionPolicy {
  4. public:
  5.   virtual Entry chooseEntryToEvict() = 0;

  6.   virtual void entryCreated(const Entry &entry) = 0;

  7.   virtual void entryReferenced(const Entry &entry) = 0;

  8.   // 如果维护引用计数的话需要这个接口
  9.   // virtual void entryUnreferenced(const Entry &entry) = 0;

  10.   virtual void entryEvicted(const Entry &entry) = 0;
  11. };


  12. // Eviction policy的类型
  13. enum class EvictionPolicyType : int {
  14.   kLRU = 0,
  15.   kLFU,
  16.   kClock,
  17.   // ...
  18. };


  19. // 一个简单的LRU实现,不支持并发访问
  20. template <typename Entry>
  21. class LRUPolicy : public EvictionPolicy<Entry> {
  22. public:
  23.   Entry chooseEntryToEvict() override {
  24.     return list_.back();
  25.   }

  26.   void entryCreated(const Entry &entry) override {
  27.     list_.push_front(entry);
  28.     index_.emplace(entry, list_.begin());
  29.   }

  30.   void entryReferenced(const Entry &entry) override {
  31.     const auto &it = index_.at(entry);
  32.     if (it != list_.begin()) {
  33.       list_.splice(list_.begin(), list_, it, std::next(it));
  34.     }
  35.   }

  36.   void entryEvicted(const Entry &entry) override {
  37.      const auto it = index_.find(entry);
  38.      list_.erase(it->second);
  39.      index_.erase(it);
  40.   }

  41. private:
  42.   using EntryList = std::list<Entry>;
  43.   using EntryListIterator = typename EntryList::iterator;

  44.   std::list<Entry> list_;
  45.   std::unordered_map<Entry, const EntryListIterator> index_;
  46. };


  47. // 一个简单的LFU实现,不支持并发访问
  48. template <typename Entry>
  49. class LFUPolicy : public EvictionPolicy<Entry> {
  50. public:
  51.   Entry chooseEntryToEvict() override {
  52.     return std::get<0>(*list_.begin());
  53.   }

  54.   void entryCreated(const Entry &entry) override {
  55.     const auto ret = list_.emplace(entry, Clock::now(), 1);
  56.     index_.emplace(entry, ret.first);
  57.   }

  58.   void entryReferenced(const Entry &entry) override {
  59.     auto &it = index_.at(entry);
  60.     const std::size_t count = std::get<2>(*it);
  61.     list_.erase(it);
  62.     const auto ret = list_.emplace(entry, Clock::now(), count + 1);
  63.     it = ret.first;
  64.   }

  65.   void entryEvicted(const Entry &entry) override {
  66.     const auto it = index_.find(entry);
  67.     list_.erase(it->second);
  68.     index_.erase(it);
  69.   }

  70. private:
  71.   using Clock = std::chrono::high_resolution_clock;
  72.   using TimePoint = std::chrono::time_point<Clock>;
  73.   using Record = std::tuple<Entry, TimePoint, std::size_t>;
  74.   struct RecordComparator {
  75.     inline bool operator()(const Record &lhs, const Record &rhs) const {
  76.       if (std::get<2>(lhs) != std::get<2>(rhs)) {
  77.         return std::get<2>(lhs) < std::get<2>(rhs);
  78.       }
  79.       if (std::get<1>(lhs) != std::get<1>(rhs)) {
  80.         return std::get<1>(lhs) < std::get<1>(rhs);
  81.       }
  82.       return std::get<0>(lhs) < std::get<0>(rhs);
  83.     }
  84.   };
  85.   using SortedList = std::set<Record, RecordComparator>;
  86.   using SortedListIterator = typename SortedList::iterator;

  87.   SortedList list_;
  88.   std::unordered_map<Entry, SortedListIterator> index_;
  89. };


  90. // Eviction policy的单例工厂
  91. template <typename Entry>
  92. class EvictionPolicyFactory {
  93. public:
  94.   static const EvictionPolicyFactory& Instance() {
  95.     static EvictionPolicyFactory<Entry> instance;
  96.     return instance;
  97.   }

  98.   EvictionPolicy<Entry>* createEvictionPolicy(const EvictionPolicyType type) const {
  99.     switch (type) {
  100.       case EvictionPolicyType::kLRU:
  101.         return new LRUPolicy<Entry>();
  102.       case EvictionPolicyType::kLFU:
  103.         return new LFUPolicy<Entry>();
  104.       default:
  105.         break;
  106.     }
  107.     throw std::runtime_error("Unsupported eviction policy type");
  108.   };
  109. };


  110. // 一个简单的泛型Cache实现
  111. template <typename Key, typename Value>
  112. class Cache {
  113. private:
  114.   using Storage = std::unordered_map<Key, Value>;
  115.   using Entry = const typename Storage::value_type*;
  116.   using Factory = EvictionPolicyFactory<Entry>;

  117. public:
  118.   Cache(const std::size_t capacity, const EvictionPolicyType type)
  119.       : capacity_(capacity),
  120.         eviction_policy_(Factory::Instance().createEvictionPolicy(type)) {
  121.   }

  122.   const Value* get(const Key &key) {
  123.     const auto it = storage_.find(key);
  124.     if (it == storage_.end()) {
  125.       return nullptr;
  126.     }

  127.     eviction_policy_->entryReferenced(&(*it));
  128.     return &it->second;
  129.   }

  130.   void put(const Key &key, const Value &value) {
  131.     const auto it = storage_.find(key);
  132.     if (it == storage_.end()) {
  133.       if (storage_.size() >= capacity_) {
  134.         const auto ev = eviction_policy_->chooseEntryToEvict();
  135.         eviction_policy_->entryEvicted(ev);
  136.         storage_.erase(ev->first);
  137.       }
  138.       const auto ret = storage_.emplace(key, value);
  139.       eviction_policy_->entryCreated(&(*ret.first));
  140.     } else {
  141.       it->second = value;
  142.       eviction_policy_->entryReferenced(&(*it));
  143.     }
  144.   }

  145. private:
  146.   const std::size_t capacity_;
  147.   Storage storage_;
  148.   std::unique_ptr<EvictionPolicy<Entry>> eviction_policy_;
  149. };

  150. /*******************************************************************************
  151. * 可以用于提交LeetCode 146的代码

  152. class LRUCache {
  153. public:
  154.   LRUCache(int capacity)
  155.       : cache_(capacity, EvictionPolicyType::kLRU) {
  156.   }

  157.   int get(int key) {
  158.     const int *value = cache_.get(key);
  159.     return value == nullptr ? -1 : *value;
  160.   }

  161.   void put(int key, int value) {
  162.     cache_.put(key, value);
  163.   }

  164. private:
  165.   Cache<int, int> cache_;
  166. };

  167. *******************************************************************************/

  168. template <typename C, typename T>
  169. void Print(C &cache, const T &key) {
  170.   const auto *value = cache.get(key);
  171.   if (value == nullptr) {
  172.     std::cout << "[" << key << "] NULL\n";
  173.   } else {
  174.     std::cout << "[" << key << "] " << *value << "\n";
  175.   }
  176. }

Wednesday, January 13, 2016

Composite Design Pattern



http://www.dofactory.com/net/composite-design-pattern
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
  • Component   (DrawingElement)
    • declares the interface for objects in the composition.
    • implements default behavior for the interface common to all classes, as appropriate.
    • declares an interface for accessing and managing its child components.
    • (optional) defines an interface for accessing a component's parent in the recursive structure, and implements it if that's appropriate.
  • Leaf   (PrimitiveElement)
    • represents leaf objects in the composition. A leaf has no children.
    • defines behavior for primitive objects in the composition.
  • Composite   (CompositeElement)
    • defines behavior for components having children.
    • stores child components.
    • implements child-related operations in the Component interface.
  • Client  (CompositeApp)
    • manipulates objects in the composition through the Component interface.
http://www.tutorialspoint.com/design_pattern/composite_pattern.htm
Composite pattern is used where we need to treat a group of objects in similar way as a single object. Composite pattern composes objects in term of a tree structure to represent part as well as whole hierarchy. 
public class Employee { private List<Employee> subordinates;}

https://sourcemaking.com/design_patterns/composite
  • Compose objects into tree structures to represent whole-part hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
  • Recursive composition
  • "Directories contain entries, each of which could be a directory."
  • 1-to-many "has a" up the "is a" hierarchy
Define an abstract base class (Component) that specifies the behavior that needs to be exercised uniformly across all primitive and composite objects. Subclass the Primitive and Composite classes off of the Component class. Each Composite object "couples" itself only to the abstract type Component as it manages its "children".
Use this pattern whenever you have "composites that contain components, each of which could be a composite".
Child management methods [e.g. addChild()removeChild()] should normally be defined in the Composite class. Unfortunately, the desire to treat Primitives and Composites uniformly requires that these methods be moved to the abstract Component class. 
  1. Create a "lowest common denominator" interface that makes your containers and containees interchangeable. It should specify the behavior that needs to be exercised uniformly across all containee and container objects.
  2. All container and containee classes declare an "is a" relationship to the interface.
  3. All container classes declare a one-to-many "has a" relationship to the interface.
  4. Container classes leverage polymorphism to delegate to their containee objects.
Examples:
interface AbstractFile:   public void ls();
class File implements AbstractFile
class Directory implements AbstractFile


Thursday, December 31, 2015

Memento Design Pattern



https://sourcemaking.com/design_patterns/memento
Intent
  • Without violating encapsulation, capture and externalize an object's internal state so that the object can be returned to this state later.
  • A magic cookie that encapsulates a "check point" capability.
  • Promote undo or rollback to full object status.
Need to restore an object back to its previous state (e.g. "undo" or "rollback" operations).

Motivation
It is sometimes necessary to capture the internal state of an object at some point and have the ability to restore the object to that state later in time. Such a case is useful in case of error or failure. Consider the case of a calculator object with an undo operation such a calculator could simply maintain a list of all previous operation that it has performed and thus would be able to restore a previous calculation it has performed. This would cause the calculator object to become larger, more complex, and heavyweight, as the calculator object would have to provide additional undo functionality and should maintain a list of all previous operations. This functionality can be moved out of the calculator class, so that an external (let's call it undo manager class) can collect the internal state of the calculator and save it. However providing the explicit access to every state variable of the calculator to the restore manager would be impractical and would violate the encapsulation principle.

  • The intent of this pattern is to capture the internal state of an object without violating encapsulation and thus providing a mean for restoring the object into initial state when needed.

Memento Pattern Implementation - UML Class Diagram 
  • Memento
    • Stores internal state of the Originator object. The state can include any number of state variables.
    • The Memento must have two interfaces, an interface to the caretaker. This interface must not allow any operations or any access to internal state stored by the memento and thus honors encapsulation. The other interface is to the originator and allows the originator to access any state variables necessary to for the originator to restore previous state.
  • Originator
    • Creates a memento object capturing the originators internal state.
    • Use the memento object to restore its previous state.
  • Caretaker
    • Responsible for keeping the memento.
    • The memento is opaque to the caretaker, and the caretaker must not operate on it.
A Caretaker would like to perform an operation on the Originator while having the possibility to rollback. The caretaker calls the createMemento() method on the originator asking the originator to pass it a memento object. At this point the originator creates a memento object saving its internal state and passes the memento to the caretaker. The caretaker maintains the memento object and performs the operation. In case of the need to undo the operation, the caretaker calls the setMemento() method on the originator passing the maintained memento object. The originator would accept the memento, using it to restore its previous state.

http://www.journaldev.com/1734/memento-design-pattern-in-java-example-tutorial
Memento design pattern is used when we want to save the state of an object so that we can restore later on. Memento pattern is used to implement this in such a way that the saved state data of the object is not accessible outside of the object, this protects the integrity of saved state data.
Memento pattern is implemented with two objects – Originator and Caretaker. Originator is the object whose state needs to be saved and restored and it uses an inner class to save the state of Object. The inner class is called Memento and its private, so that it can’t be accessed from other objects.
Caretaker is the helper class that is responsible for storing and restoring the Originator’s state through Memento object. Since Memento is private to Originator, Caretaker can’t access it and it’s stored as a Object within the caretaker.
http://www.avajava.com/tutorials/lessons/memento-pattern.html
The memento pattern is a behavioral design pattern. The memento pattern is used to store an object's state so that this state can be restored at a later point. The saved state data in the memento object is not accessible outside of the object to be saved and restored. This protects the integrity of the saved state data.
In this pattern, an Originator class represents the object whose state we would like to save. A Memento class represents an object to store the state of the Originator. The Memento class is typically a private inner class of the Originator. As a result, the Originator has access to the fields of the memento, but outside classes do not have access to these fields. This means that state information can be transferred between the Memento and the Originator within the Originator class, but outside classes do not have access to the state data stored in the Memento.
The memento pattern also utilizes a Caretaker class. This is the object that is responsible for storing and restoring the Originator's state via a Memento object. Since the Memento is a private inner class, the Memento class type is not visible to the Caretaker. As a result, the Memento object needs to be stored as an Object within the Caretaker.
// originator - object whose state we want to save
public class DietInfo {

 String personName;
 int dayNumber;
 int weight;

 public DietInfo(String personName, int dayNumber, int weight) {
  this.personName = personName;
  this.dayNumber = dayNumber;
  this.weight = weight;
 }

 public String toString() {
  return "Name: " + personName + ", day number: " + dayNumber + ", weight: " + weight;
 }

 public void setDayNumberAndWeight(int dayNumber, int weight) {
  this.dayNumber = dayNumber;
  this.weight = weight;
 }

 public Memento save() {
  return new Memento(personName, dayNumber, weight);
 }

 public void restore(Object objMemento) {
  Memento memento = (Memento) objMemento;
  personName = memento.mementoPersonName;
  dayNumber = memento.mementoDayNumber;
  weight = memento.mementoWeight;
 }

 // memento - object that stores the saved state of the originator
 private class Memento {
  String mementoPersonName;
  int mementoDayNumber;
  int mementoWeight;

  public Memento(String personName, int dayNumber, int weight) {
   mementoPersonName = personName;
   mementoDayNumber = dayNumber;
   mementoWeight = weight;
  }
 }
}
// caretaker - saves and restores a DietInfo object's state via a memento
// note that DietInfo.Memento isn't visible to the caretaker so we need to cast the memento to Object
public class DietInfoCaretaker {

 Object objMemento;

 public void saveState(DietInfo dietInfo) {
  objMemento = dietInfo.save();
 }

 public void restoreState(DietInfo dietInfo) {
  dietInfo.restore(objMemento);
 }

}
http://www.tutorialspoint.com/design_pattern/memento_pattern.htm

One of the best real life example is the text editors where we can save it’s data anytime and use undo to restore it to previous saved state. 
public class FileWriterUtil {
    private String fileName;
    private StringBuilder content;
     
    public FileWriterUtil(String file){
        this.fileName=file;
        this.content=new StringBuilder();
    }
     
    @Override
    public String toString(){
        return this.content.toString();
    }
     
    public void write(String str){
        content.append(str);
    }
     
    public Memento save(){
        return new Memento(this.fileName,this.content);
    }
     
    public void undoToLastSave(Object obj){
        Memento memento = (Memento) obj;
        this.fileName= memento.fileName;
        this.content=memento.content;
    }
     
     
    private class Memento{
        private String fileName;
        private StringBuilder content;
         
        public Memento(String file, StringBuilder content){
            this.fileName=file;
            //notice the deep copy so that Memento and FileWriterUtil content variables don't refer to same object
            this.content=new StringBuilder(content);
        }
    }
}
Notice the Memento inner class and implementation of save and undo methods.
public class FileWriterCaretaker {
    private Object obj;
     
    public void save(FileWriterUtil fileWriter){
        this.obj=fileWriter.save();
    }
     
    public void undo(FileWriterUtil fileWriter){
        fileWriter.undoToLastSave(obj);
    }
}
Notice that caretaker object contains the saved state in the form of Object, so it can’t alter its data and also it has no knowledge of it’s structure.

one of the thing needs to take care is that Memento class should be accessible only to the Originator object. Also in client application, we should use caretaker object for saving and restoring the originator state.
Also if Originator object has properties that are not immutable, we should use deep copy or cloning to avoid data integrity issue like I have used in above example. We can use Serialization to achieve memento pattern implementation that is more generic rather than Memento pattern where every object needs to have it’s own Memento class implementation.
One of the drawback is that if Originator object is very huge then Memento object size will also be huge and use a lot of memory.
http://www.javacodegeeks.com/2015/09/memento-design-pattern.html
03public class Originator {
04
05    private double x;
06    private double y;
07
08    private String lastUndoSavepoint;
09    CareTaker careTaker;
10
11    public Originator(double x, double y,CareTaker careTaker){
12        this.x = x;
13        this.y = y;
14
15        this.careTaker = careTaker;
16
17        createSavepoint("INITIAL");
18    }
36    public void createSavepoint(String savepointName){
37        careTaker.saveMemento(new Memento(this.x, this.y), savepointName);
38        lastUndoSavepoint = savepointName;
39    }
40
41    public void undo(){
42        setOriginatorState(lastUndoSavepoint);
43    }
44
45    public void undo(String savepointName){
46        setOriginatorState(savepointName);
47    }
48
49    public void undoAll(){
50        setOriginatorState("INITIAL");
51        careTaker.clearSavepoints();
52    }
53
54    private void setOriginatorState(String savepointName){
55        Memento mem = careTaker.getMemento(savepointName);
56        this.x = mem.getX();
57        this.y = mem.getY();
58    }
65}
03public class Memento {
04
05    private double x;
06    private double y;
07
08    public Memento(double x, double y){
09        this.x = x;
10        this.y = y;
11    }
20}
The Memento class is used to store the state of the Originator and stored by the care taker. The class does not have any setter methods, it is only used to get the state of the object.
06public class CareTaker {
07
08    private final Map<String, Memento>savepointStorage = newHashMap<String, Memento>();
09
10    public void saveMemento(Memento memento,String savepointName){
11        System.out.println("Saving state..."+savepointName);
12        savepointStorage.put(savepointName, memento);
13    }
14
15    public Memento getMemento(String savepointName){
16        System.out.println("Undo at ..."+savepointName);
17        return savepointStorage.get(savepointName);
18    }
19
20    public void clearSavepoints(){
21        System.out.println("Clearing all save points...");
22        savepointStorage.clear();
23    }
25}

4. When to use the Memento Pattern
Use the Memento Pattern in the following cases:
A snapshot of (some portion of) an object’s state must be saved so that it can be restored to that state later, and
A direct interface to obtaining the state would expose implementation details and break the object’s encapsulation.
5. Memento Pattern in JDK
java.util.Date
java.io.Serializable

Some problems with this pattern is that the saving or restoring of state can be a time consuming process. Used incorrectly, it can expose the internal structure of your object, thus allowing any other object to change the state of your object.
'The memento pattern seems to be more flexible. You ask an object for its current state, and it can track whatever changes are made using special semantics, rather than dumping the entire state and rereading later.' -- PeterSchofield

http://www.cnblogs.com/java-my-life/archive/2012/06/06/2534942.html
  备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。
  备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉(Capture)住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代子模式一同使用。

备忘录模式的结构

  备忘录模式的结构图如下所示
  备忘录模式所涉及的角色有三个:备忘录(Memento)角色、发起人(Originator)角色、负责人(Caretaker)角色

  备忘录(Memento)角色

  备忘录角色又如下责任:
  (1)将发起人(Originator)对象的内战状态存储起来。备忘录可以根据发起人对象的判断来决定存储多少发起人(Originator)对象的内部状态。
  (2)备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。
  备忘录有两个等效的接口:
  ●  窄接口:负责人(Caretaker)对象(和其他除发起人对象之外的任何对象)看到的是备忘录的窄接口(narrow interface),这个窄接口只允许它把备忘录对象传给其他的对象。
  ●  宽接口:与负责人对象看到的窄接口相反的是,发起人对象可以看到一个宽接口(wide interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。

  发起人(Originator)角色

  发起人角色有如下责任:
  (1)创建一个含有当前的内部状态的备忘录对象。
  (2)使用备忘录对象存储其内部状态。

  负责人(Caretaker)角色

  负责人角色有如下责任:
  (1)负责保存备忘录对象。
  (2)不检查备忘录对象的内容。

 “白箱”备忘录模式的实现

  备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。因此这个实现又叫做“白箱实现”。
  “白箱”实现将发起人角色的状态存储在一个大家都看得到的地方,因此是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。因此白箱实现仍然是有意义的。
public class Originator {

    private String state;
    /**
     * 工厂方法,返回一个新的备忘录对象
     */
    public Memento createMemento(){
        return new Memento(state);
    }
    /**
     * 将发起人恢复到备忘录对象所记载的状态
     */
    public void restoreMemento(Memento memento){
        this.state = memento.getState();
    }
    
    public String getState() {
        return state;
    }
    
    public void setState(String state) {
        this.state = state;
        System.out.println("当前状态:" + this.state);
    }
    
}
public class Caretaker {

    private Memento memento;
    /**
     * 备忘录的取值方法
     */
    public Memento retrieveMemento(){
        return this.memento;
    }
    /**
     * 备忘录的赋值方法
     */
    public void saveMemento(Memento memento){
        this.memento = memento;
    }
}
    public static void main(String[] args) {
        
        Originator o = new Originator();
        Caretaker c = new Caretaker();
        //改变负责人对象的状态
        o.setState("On");
        //创建备忘录对象,并将发起人对象的状态储存起来
        c.saveMemento(o.createMemento());
        //修改发起人的状态
        o.setState("Off");
        //恢复发起人对象的状态
        o.restoreMemento(c.retrieveMemento());
        
        System.out.println(o.getState());
    }

 “黑箱”备忘录模式的实现

  备忘录角色对发起人(Originator)角色对象提供一个宽接口,而为其他对象提供一个窄接口。这样的实现叫做“黑箱实现”。
  在JAVA语言中,实现双重接口的办法就是将备忘录角色类设计成发起人角色类的内部成员类。
  将Memento设成Originator类的内部类,从而将Memento对象封装在Originator里面;在外部提供一个标识接口MementoIF给Caretaker以及其他对象。这样,Originator类看到的是Menmento的所有接口,而Caretaker以及其他对象看到的仅仅是标识接口MementoIF所暴露出来的接口。
  使用内部类实现备忘录模式的类图如下所示。
  发起人角色类Originator中定义了一个内部的Memento类。由于此Memento类的全部接口都是私有的,因此只有它自己和发起人类可以调用。
public class Originator {

    private String state;
    
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
        System.out.println("赋值状态:" + state);
    }
    /**
     * 工厂方法,返还一个新的备忘录对象
     */
    public MementoIF createMemento(){
        return new Memento(state);
    }
    /**
     * 发起人恢复到备忘录对象记录的状态
     */
    public void restoreMemento(MementoIF memento){
        this.setState(((Memento)memento).getState());
    }
    
    private class Memento implements MementoIF{
        
        private String state;
        /**
         * 构造方法
         */
        private Memento(String state){
            this.state = state;
        }
        
        private String getState() {
            return state;
        }
        private void setState(String state) {
            this.state = state;
        }
    }
}
接口MementoIF,这是一个标识接口,因此它没有定义出任何的方法。

public interface MementoIF {

}
  负责人角色类Caretaker能够得到的备忘录对象是以MementoIF为接口的,由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容。
public class Caretaker {

    private MementoIF memento;
    /**
     * 备忘录取值方法
     */
    public MementoIF retrieveMemento(){
        return memento;
    }
    /**
     * 备忘录赋值方法
     */
    public void saveMemento(MementoIF memento){
        this.memento = memento;
    }
}

多重检查点

  前面所给出的白箱和黑箱的示意性实现都是只存储一个状态的简单实现,也可以叫做只有一个检查点。常见的系统往往需要存储不止一个状态,而是需要存储多个状态,或者叫做有多个检查点。
  备忘录模式可以将发起人对象的状态存储到备忘录对象里面,备忘录模式可以将发起人对象恢复到备忘录对象所存储的某一个检查点上

 “自述历史”模式

  所谓“自述历史”模式(History-On-Self Pattern)实际上就是备忘录模式的一个变种。在备忘录模式中,发起人(Originator)角色、负责人(Caretaker)角色和备忘录(Memento)角色都是独立的角色。虽然在实现上备忘录类可以成为发起人类的内部成员类,但是备忘录类仍然保持作为一个角色的独立意义。在“自述历史”模式里面,发起人角色自己兼任负责人角色。
 备忘录角色有如下责任:
  (1)将发起人(Originator)对象的内部状态存储起来。
  (2)备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。
  发起人角色有如下责任:
  (1)创建一个含有它当前的内部状态的备忘录对象。
  (2)使用备忘录对象存储其内部状态。
  客户端角色有负责保存备忘录对象的责任。

  源代码

  窄接口MementoIF,这是一个标识接口,因此它没有定义出任何的方法。
public interface MementoIF {

}
  发起人角色同时还兼任负责人角色,也就是说它自己负责保持自己的备忘录对象。
  由于“自述历史”作为一个备忘录模式的特殊实现形式非常简单易懂,它可能是备忘录模式最为流行的实现形式。


Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts