2013-02-26 17 views
5

Załóżmy, że scenariusz nie pozwala na implementację typu niezmiennego. Podążając za tym założeniem, chciałbym uzyskać opinie/przykłady, jak poprawnie zaprojektować typ, który po jego zużyciu staje się niezmienny.Zaprojektuj klasę zmienną, która po jej zużyciu stanie się niezmienna.

public class ObjectAConfig { 

    private int _valueB; 
    private string _valueA; 
    internal bool Consumed { get; set; } 

    public int ValueB { 
    get { return _valueB; } 
    set 
    { 
     if (Consumed) throw new InvalidOperationException(); 
     _valueB = value; 
    } 
    } 

    public string ValueA { 
    get { return _valueA; } 
    set 
    { 
     if (Consumed) throw new InvalidOperationException(); 
     _valueA = value; 
    } 
    } 
} 

Kiedy ObjectA zużywa ObjectAConfig:

public ObjectA { 

    public ObjectA(ObjectAConfig config) { 

    _config = config; 
    _config.Consumed = true; 
    } 
} 

nie jestem zadowolony, że to po prostu działa, chciałbym wiedzieć, czy istnieje lepszy wzór (wyłączone, jak powiedział, co ObjectAConfig niezmienna przez projekt od początku).

Na przykład:

  • może sensu definiować monady jak Once<T> które pozwalają owinięty wartość zostać zainicjowany tylko raz?

  • może w sensie zdefiniować typ, który zwraca sam typ zmieniający prywatne pole?

+0

* Dlaczego * nie możesz wprowadzić niezmiennego typu? Pomogłoby to, gdybyś wyjaśnił prawdziwy problem, który próbujesz rozwiązać. –

+0

[To pytanie] (http://stackoverflow.com/questions/4168382) ma kilka interesujących wzorców robienia podobnych rzeczy. –

Odpowiedz

10

Co wdrażają czasami idzie pod nazwą „popsicle immutability” - to znaczy można go zamrażać. Twoje obecne podejście zadziała - w rzeczywistości używam tego wzoru w wielu miejscach.

Prawdopodobnie można zmniejszyć niektóre powielania poprzez coś takiego:

private void SetField<T>(ref T field, T value) { 
    if (Consumed) throw new InvalidOperationException(); 
    field = value; 
} 
public int ValueB { 
    get { return _valueB; } 
    set { SetField(ref _valueB, value); } 
}  
public string ValueA { 
    get { return _valueA; } 
    set { SetField(ref _valueA, value); } 
} 

istnieje inny, powiązany podejście, choć: wypełniacz. Na przykład, biorąc istniejącą klasę:

public interface IConfig 
{ 
    string ValueA { get; } 
    int ValueB { get; } 
} 
public class ObjectAConfig : IConfig 
{ 
    private class ImmutableConfig : IConfig { 
     private readonly string valueA; 
     private readonly int valueB; 
     public ImmutableConfig(string valueA, int valueB) 
     { 
      this.valueA = valueA; 
      this.valueB = valueB; 
     } 
    } 
    public IConfig Build() 
    { 
     return new ImmutableConfig(ValueA, ValueB); 
    } 
    ... snip: implementation of ObjectAConfig 
} 

Tu jest naprawdę niezmienne realizacja IConfig i oryginalne wykonanie. Jeśli chcesz zamrożoną wersję, zadzwoń pod numer Build().

+0

+1 @Marc Gravell, Naprawdę doceniam próbki; ale jeszcze bardziej doceniam ten wzorzec jako nazwę z solidną podstawową koncepcją. Będę również nurkować w artykułach E.Lipperta. – jay

+0

Aby odporność na popsicle była bezpieczna dla wątków, wszystkie metody ustawiania obiektów i metoda zamrożenia muszą korzystać z blokowania lub innych podobnych środków, aby zapewnić, że obiekt nie może zostać zamrożony podczas modyfikowania [możliwe jest użycie blokowanych prymitywów zamiast tego, jeśli taki jest. chętny do użycia niepoprawnego wątku wywołuje wyjątek w metodzie 'freeze'. Zauważ, że nawet jeśli klasa nie reklamuje się jako bezpieczna dla wątków, powinna jednak upewnić się, że jeśli instancja podała, że ​​jest niezmienna i zgłosiła jakikolwiek aspekt jej stanu, aspekt ten nigdy się nie zmieni. – supercat

+0

@supercar w wielu przypadkach rozsądne jest założenie, że coś będzie zapełnione w odizolowanym obszarze, zamrożone, a *** tylko wtedy *** narażone na wiele wątków. –

Powiązane problemy