Collezioni di elementi reattivi generici

1

Nell'estensione dell'editor dei temi di colore di Visual Studio se cambi alcuni colori, alcuni altri colori correlati cambieranno di conseguenza. Possono diventare uguali al colore modificato o essere più chiari / scuri per esempio.

Mi sono chiesto come può essere implementato questo comportamento il più generale possibile. Quindi, ho pensato a questa pazza soluzione.

using System;
using System.Collections.Generic;
using System.Drawing;

namespace tester {

interface IKeyGet<TKey, TValue> {
    TValue Get(TKey key);
}

interface IKeySet<TKey, TValue> {
    void Set(TKey key, TValue value);
}

interface IKeyGetSet<TKey, TValue> : IKeyGet<TKey, TValue>, IKeySet<TKey, TValue> {
}

class SomeManager<TKey, TValue> : IKeyGetSet<TKey, TValue> {
    protected ICollection<IKeyGet<TKey, TValue>> Getters { get; set; }
    protected ICollection<IKeySet<TKey, TValue>> Setters { get; set; }

    public SomeManager(ICollection<IKeyGet<TKey, TValue>> getters, ICollection<IKeySet<TKey, TValue>> setters) {
        Getters = getters;
        Setters = setters;
    }

    public virtual TValue Get(TKey key) {
        var value = default(TValue);
        foreach (var g in Getters) {
            value = g.Get(key);
        }
        return value;
    }

    public virtual void Set(TKey key, TValue value) {
        foreach (var s in Setters) {
            s.Set(key, value);
        }
    }
}

// Gets value by key, throws if key is not found
class BasicGetter<TKey, TValue> : IKeyGet<TKey, TValue> {
    public IDictionary<TKey, TValue> Source { get; set; }

    public BasicGetter(IDictionary<TKey, TValue> source) {
        Source = source;
    }

    public TValue Get(TKey key) {
        return Source[key];
    }
}

// Can add new key-value pair or update existing
class CreateUpdateSetter<TKey, TValue> : IKeySet<TKey, TValue> {
    public IDictionary<TKey, TValue> Source { get; set; }

    public CreateUpdateSetter(IDictionary<TKey, TValue> source) {
        Source = source;
    }

    public void Set(TKey key, TValue value) {
        Source[key] = value;
    }
}

// Can only update existing key-value pairs, adding new ones is forbidden
class UpdateSetter<TKey, TValue> : IKeySet<TKey, TValue> {
    public IDictionary<TKey, TValue> Source { get; set; }

    public UpdateSetter(IDictionary<TKey, TValue> source) {
        Source = source;
    }

    public void Set(TKey key, TValue value) {
        if (!Source.ContainsKey(key)) {
            throw new KeyNotFoundException();
        } else {
            Source[key] = value;
        }
    }
}

// If key is in RelatedSetters, then invoke all corresponding delegates and set the keys being returned
class RelatedSetter<TKey, TValue> : IKeySet<TKey, TValue> {
    public IDictionary<TKey, IEnumerable<Func<TKey, TValue, KeyValuePair<TKey, TValue>>>> RelatedSetters { get; set; }

    public IKeySet<TKey, TValue> Source { get; set; }

    public RelatedSetter(IKeySet<TKey, TValue> source) {
        Source = source;
        RelatedSetters = new Dictionary<TKey, IEnumerable<Func<TKey, TValue, KeyValuePair<TKey, TValue>>>>();
    }

    public void Set(TKey key, TValue value) {
        IEnumerable<Func<TKey, TValue, KeyValuePair<TKey, TValue>>> related;
        if (RelatedSetters.TryGetValue(key, out related)) {
            foreach (var f in related) {
                var c = f(key, value);
                Source.Set(c.Key, c.Value);
            }
        }
    }
}

// Has read-update behavior
class ColorManager<T> : SomeManager<T, Color> {
    private IDictionary<T, Color> _colorMap;
    private RelatedSetter<T, Color> _relatedSetter;

    public IEnumerable<T> Keys { get { return _colorMap.Keys; } }

    public IDictionary<T, IEnumerable<Func<T, Color, KeyValuePair<T, Color>>>> RelatedColors {
        get {
            return _relatedSetter.RelatedSetters;
        }
        set {
            _relatedSetter.RelatedSetters = value;
        }
    }

    public ColorManager(IDictionary<T, Color> initialColors) :
        base(new List<IKeyGet<T, Color>>(), new List<IKeySet<T, Color>>()) {

        _colorMap = initialColors;
        _relatedSetter = new RelatedSetter<T, Color>(this);

        Getters.Add(new BasicGetter<T, Color>(_colorMap));
        Setters.Add(new UpdateSetter<T, Color>(_colorMap));
        Setters.Add(_relatedSetter);
    }
}

class Program {
    enum AvailableColors { Water, Submarine, Seabed };

    static void Main(string[] args) {
        var cm = new ColorManager<AvailableColors>(new Dictionary<AvailableColors, Color> {
            { AvailableColors.Water, Color.Green },
            { AvailableColors.Submarine, Color.Red },
            { AvailableColors.Seabed, Color.Yellow }
        });

        cm.RelatedColors = new Dictionary<AvailableColors, IEnumerable<Func<AvailableColors, Color, KeyValuePair<AvailableColors, Color>>>> {
            {
                AvailableColors.Water, new List<Func<AvailableColors, Color, KeyValuePair<AvailableColors, Color>>> {
                    (k, v) => { return new KeyValuePair<AvailableColors, Color>(AvailableColors.Submarine, Color.Azure); },
                    (k, v) => { return new KeyValuePair<AvailableColors, Color>(AvailableColors.Seabed, Color.Red); }
                }
            },
            {
                AvailableColors.Seabed, new List<Func<AvailableColors, Color, KeyValuePair<AvailableColors, Color>>> {
                    (k, v) => { return new KeyValuePair<AvailableColors, Color>(AvailableColors.Submarine, Color.Black); },
                }
            }
        };

        cm.Set(AvailableColors.Water, Color.AliceBlue);

        // Expected colors: 
        //
        // Water - AliceBlue
        // Submarine - Black
        // Seabed - Red

        foreach (var key in cm.Keys) {
            Console.WriteLine($"{key}: {cm.Get(key)}");
        }
    }
}
}

L'idea di base è prendere una classe simile al dizionario, che possa ottenere e impostare i valori con le chiavi e migliorare i suoi getter e setter con un comportamento personalizzato. Penso che questo si possa ottenere meglio con i mixin, come in Python, ma siccome C # non supporta i mixin, le collezioni di funzioni li sostituiscono.

Per me, il beneficio di questa soluzione è la personalizzazione del comportamento pur conservando la semplice interfaccia Get / Set.

Anche se, ho un po 'paura delle linee intimidatorie di codice come:

public IDictionary<TKey, IEnumerable<Func<TKey, TValue, KeyValuePair<TKey, TValue>>>> RelatedSetters { get; set; }

e

cm.RelatedColors = new Dictionary<AvailableColors, IEnumerable<Func<AvailableColors, Color, KeyValuePair<AvailableColors, Color>>>> {...};

mentre in Python sarebbe solo

cm.RelatedColors = {...}

Modifica

La mia domanda è: "Come implementeresti la raccolta, in cui gli elementi possono essere correlati tra loro? Come la modifica di un elemento può far scattare cambiamenti in uno o più altri elementi."

Non c'è bisogno di esempi concreti, le teorie saranno sufficienti.

    
posta saintcrawler 02.09.2016 - 21:49
fonte

1 risposta

0

Vorrei semplicemente usare INotifyPropertyChanged .

Fondamentalmente, un dato colore non si cura di quali altri colori dipendono da esso. Inversamente, un dato colore che è basato su un altro dovrebbe essere interessato alle modifiche che si verificano al colore di base.

È possibile visualizzare lo stesso modello nelle interfacce Windows Form / WPF. Se un controllo è associato a una variabile, spetta al controllo tracciare quando la variabile cambia valore: questo è l'uso più comune di INotifyPropertyChanged .

    
risposta data 02.09.2016 - 23:13
fonte

Leggi altre domande sui tag