2015-09-24 39 views
-1

什麼應該考慮使可變類不可變?例如:我們仍然可以在不可變的堆棧類中使用push和pop方法嗎?或者我們應該簡單地刪除任何改變實例化對象狀態的方法?Mutable vs. Immutable

+3

如果你可以把一個新元素進棧,它不是一成不變的。 –

+0

如果你只修改'newElement'數組,爲什麼'this'的'size'會增長? –

+1

你想改變一個_immutable_對象的狀態嗎?如何和爲什麼? – Tom

回答

1

的底線是:你最好能夠去除法修改從類實例化對象的狀態。但是如果你想保留它,那麼它應該創建一個與原始狀態不同的新對象並返回新對象。

這裏有一個更明確的答案:

不應該有任何的不可變類改變對象狀態的方法。

有很多void方法在可變類中改變這個對象的狀態。所以我們應該以返回新對象的方式改變它們的簽名,而不是改變「this」對象的狀態。

還有很多非void方法改變「this」對象的狀態並返回它們在「this」中改變的值。這些方法的簽名也應該以它們返回新對象的方式進行更改,而不是更改「this」的狀態。談到列表,通常還需要另一種方法(如「偷看」)才能獲得一定的價值。檢查樣品波紋管得到這是什麼意思:

查閱這些「推」和一個可變的堆棧類「流行」的方法:

public class Stack <E> { 
… 
public void push (E e) { 
    ensureCapacity(); // This method checks for capacity 
    elements[size++] = e; 
    } 

這種方法在的頂部增加了一個新元素堆棧並以這種方式改變「this」對象的狀態。

public E pop() { 
    if (size == 0) throw new IllegalStateException("Stack.pop"); 
    E result = elements[--size]; 
    elements[size] = null; 
    return result; 
    } 
… 
} 

該方法刪除堆棧頂部的元素並將其返回並通過刪除元素來更改「this」對象的狀態。

現在,假設我們需要改變這些方法來使這個堆棧不可變。我們首先處理「推」的方法:

「推」是一種無效的方法,通過添加一個新元素來改變「this」對象的狀態。爲了使堆棧不變,我們將創建一個類似於「本」和新元素添加到這個新的堆棧,然後返回一個新的堆棧:

public class ImmStack <E> { 
... 
    /** 
    * this method pushes the item to a new object and keeps the original object unchanged 
    * @param e The item to be pushed 
    * @return A new list 
    * @PRE An original list object is required as well as an item to be pushed 
    * @POST A new list would be returned 
    */ 
    @SuppressWarnings({ "unchecked", "rawtypes" }) // All items in this.elements[] are of type E 
    public ImmStack<E> push (E e) { 

     ImmStack<E> stc = new ImmStack(getNewElements());  
     stc.elements=ensureCapacity(stc.elements); 
     stc.elements[size] = e; 
     stc.size = size +1;   
     return stc; 
     } 

「流行」方法,通過改變「這個」對象的狀態刪除一個元素。爲了使類不可變的,我們將reate類似「這個」,從這個新的堆棧中刪除的元素,然後返回一個新的堆棧:

/** 
    * This pop method returns a new stack without the element at the top of the original list 
    * @return The new stack 
    * @POST The new stack would be returned 
    */ 
    @SuppressWarnings({ "unchecked", "rawtypes" }) // All items in this.elements[] are of type E 
    public ImmStack<E> pop() { 

     if (size == 0) throw new IllegalStateException("Stack.pop"); 

     ImmStack<E> stc = new ImmStack(getNewElements());   
     stc.elements=ensureCapacity(stc.elements); 
     stc.elements[size-1] = null; 
     stc.size=size-1; 
     return stc; 
     } 

舊的「流行」方法是在頂部返回的元素。我們還需要一種返回頂部元素以覆蓋此功能的新方法:

/** 
    * Returns item at front of queue without removing. 
    * @return item at front 
    * @throws java.util.NoSuchElementException if empty 
    */ 
    public E top() 
    { 
    if (this.isEmpty()) 
     { 
     throw new NoSuchElementException("Queue underflow"); 
     } 
     return elements[size-1]; 
    } 

這只是一個示例。你可能有更多的方法來改變你的類,使其不可變。

1

如果你的堆棧是不可變的,那麼根據定義它是不能改變的。 push()pop()方法無法完成。

當某個方法無法成功完成時,可以拋出異常。當一個方法可以永不成功完成,拋出的標準異常是UnsupportedOperationException

例如:

public E[] push (E e) { 
    throw new UnsupportedOperationException(); 
} 

編輯:

您注意到在評論你的push()方法只是返回堆棧的深層副本與新的元素。它看起來像是將不可變堆棧表示爲一個類的實例,並將所推入的堆棧表示爲一個數組。

您可以使用newElements.length獲得newElements引用的兩個數組之一的大小。所以,你可以寫這樣的代碼:

public E[] push (E e) { 
    E[] newElements=getNewElements(); 
    int oldLength = newElements.length; 
    newElements=ensureCapacity(newElements); 
    int lastIndexInNewArray = oldLength; 
    newElements[ lastIndexInNewArray ] = e; 
    return newElements; 
} 
0

下面是C#中不可變堆棧的實現。

推動和彈出讓你回到一個全新的堆棧,並且Peek可以讓你在沒有彈出的情況下查看堆棧的頂部。
請注意,不需要複製整個堆棧。

這就是不可變結構在任何非平凡情況下的實現方式。在某些情況下,非平凡的不可變結構非常有用。海報說,這是不可能做到很多誤解。

原代碼和詳細信息可以在這裏找到:
https://blogs.msdn.microsoft.com/ericlippert/2007/12/04/immutability-in-c-part-two-a-simple-immutable-stack/

public interface IStack<T> : IEnumerable<T> 
{ 
    IStack<T> Push(T value); 
    IStack<T> Pop(); 
    T Peek(); 
    bool IsEmpty { get; } 
} 

public sealed class Stack<T> : IStack<T> 
{ 
    private sealed class EmptyStack : IStack<T> 
    { 
     public bool IsEmpty { get { return true; } } 
     public T Peek() { throw new Exception("Empty stack"); } 
     public IStack<T> Push(T value) { return new Stack<T>(value, this); } 
     public IStack<T> Pop() { throw new Exception("Empty stack"); } 
     public IEnumerator<T> GetEnumerator() { yield break; } 
     IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } 
    } 
    private static readonly EmptyStack empty = new EmptyStack(); 
    public static IStack<T> Empty { get { return empty; } } 
    private readonly T head; 
    private readonly IStack<T> tail; 
    private Stack(T head, IStack<T> tail) 
    { 
     this.head = head; 
     this.tail = tail; 
    } 
    public bool IsEmpty { get { return false; } } 
    public T Peek() { return head; } 
    public IStack<T> Pop() { return tail; } 
    public IStack<T> Push(T value) { return new Stack<T>(value, this); } 
    public IEnumerator<T> GetEnumerator() 
    { 
     for(IStack<T> stack = this; !stack.IsEmpty ; stack = stack.Pop()) 
      yield return stack.Peek(); 
    } 
    IEnumerator IEnumerable.GetEnumerator() {return this.GetEnumerator();} 
}