首页 > 解决方案 > 如何在不修改状态的情况下共享对象列表?

问题描述

假设我有一个包含Companies列表的StockMarket类。

class StockMarket : IStock
{
    private static List<IObserverPush> observersPush;
    private static List<IObserverPull> observersPull;
    public static List<Company> Companies { get; private set; }

    public StockMarket()
    {
        observersPush = new List<IObserverPush>();
        observersPull = new List<IObserverPull>();

        Companies = new List<Company>() { new Company("Unilever", "UNA", 47.72, 0.77, 1.63, -3.45, "135B"),
                                            new Company("ING Groep", "INGA", 13.40, -0.07, -0.50, -12.38, "60.4B"),
                                            new Company("ArcelorMittal", "MT", 29.50, 0.14, 0.48, 36.05, "54.6B"),
                                            new Company("ASML Holding", "ASML", 167.40, 2.00, 1.21, 36.49, "53.3B"),
                                            new Company("Heineken", "HEIA", 87.66, -0.02, -0.02, 2.80, "49B"),
                                            new Company("RELX", "REN", 18.15, 0.17, 0.95, -0.22, "38.9B"),
                                            new Company("Philips", "PHIA", 35.49, 0.17, 0.47, 7.61, "33.3B"),
                                            new Company("Unibail Rodamco", "UL", 196.40, -0.15, -0.08, -16.78, "20.3B"),
                                            new Company("Akzo Nobel", "AKZA", 75.68, -0.16, -0.21, 0.33, "19.4B"),
                                            new Company("Altice", "ATC", 7.58, 0.16, 2.16, -66.30, "17.6B")};

        Thread thread = new Thread(SimulateMarket);
        thread.Start();
    }
    public void Subscribe(IObserverPull o)
    {
        observersPull.Add(o);
        o.UpdateMarket();
    }
    public void Unsubscribe(IObserverPull o)
    {
        observersPull.Remove(o);
    }
    public void Subscribe(IObserverPush o)
    {
        observersPush.Add(o);
        o.UpdateMarket(Companies);
    }
    public void Unsubscribe(IObserverPush o)
    {
        observersPush.Remove(o);
    }
    public void NotifyObservers()
    {
        foreach(IObserverPush o in observersPush)
        {
            o.UpdateMarket(Companies);
        }

        foreach(IObserverPull o in observersPull)
        {
            o.UpdateMarket();
        }
    }

    public void SimulateMarket()
    {
        while(observersPush.Count + observersPull.Count > 0)
        {
            //randomly change property values of companies
            //and notify the observers about the changes
        }
    }
}

公司类有一些属性。

public class Company
{
    public string Name { get; private set; }
    public string Symbol { get; private set; }
    public double Price { get; set; }
    public double Change { get; set; }
    public double ChangePercentageDay { get; set; }
    public double ChangePercentageYear { get; set; }
    public string Capital { get; private set; }

    public Company(string name, string symbol, double price, double change, double changePercentageDay, 
                    double changePercentageYear, string capital)
    {
        Name = name;
        Symbol = symbol;
        Price = price;
        Change = change;
        ChangePercentageDay = changePercentageDay;
        ChangePercentageYear = changePercentageYear;
        Capital = capital;
    }
}

表单引用了StockMarket,他们使用它来检索有关公司的数据并显示它。

表格 1

public partial class ConcreteObserverPush : Form, IObserverPush
{
    private StockMarket stockMarket;
    public ConcreteObserverPush()
    {
        InitializeComponent();
        stockMarket = new StockMarket();
        stockMarket.Subscribe(this);
    }

    public void UpdateMarket(List<Company> companies)
    {
        stockMarketListView.Items.Clear();

        foreach(Company c in companies)
        {
            ListViewItem item = new ListViewItem(c.Symbol);
            item.SubItems.Add(c.Price.ToString());
            item.SubItems.Add(c.Change.ToString());
            item.SubItems.Add(c.ChangePercentageDay.ToString() + "%");
            stockMarketListView.Items.Add(item);
        }
    }

    private void ConcreteObserverPush_FormClosing(object sender, FormClosingEventArgs e)
    {
        stockMarket.Unsubscribe(this);
    }
}

表格 2

public partial class ConcreteObserverPull : Form, IObserverPull
{
    private StockMarket stockMarket;
    public ConcreteObserverPull()
    {
        InitializeComponent();
        stockMarket = new StockMarket();
        stockMarket.Subscribe(this);
    }

    public void UpdateMarket()
    {
        stockMarketListView.Items.Clear();

        foreach (Company c in StockMarket.Companies)
        {
            ListViewItem item = new ListViewItem(c.Symbol);
            item.SubItems.Add(c.Name);
            item.SubItems.Add(c.Price.ToString());
            item.SubItems.Add(c.Change.ToString());
            item.SubItems.Add(c.ChangePercentageDay.ToString() + "%");
            item.SubItems.Add(c.ChangePercentageYear.ToString() + "%");
            item.SubItems.Add(c.Capital);
            stockMarketListView.Items.Add(item);
        }
    }

    private void ConcreteObserverPull_FormClosing(object sender, FormClosingEventArgs e)
    {
        stockMarket.Unsubscribe(this);
    }
}

问题是,如果表单通过StockMarket上的属性获取公司列表,它可以更改它们的状态。但是,我只希望 StockMarket 有能力改变公司的状态。

那么在请求时与表单共享公司状态并防止表单修改它的最佳方式是什么。

我知道一个可能的解决方案是返回Company对象的克隆,但我相信应该有更好的解决方案。

任何帮助表示赞赏!

标签: c#

解决方案


这样做的一般要点是使您的Company对象不可变。然后,您将向 StockMarket 对象添加方法来操作 Company 列表,并在您想要更改值时用新条目替换条目。

这是一个放在 LINQPad 中的快速示例,用于使Company类不可变并向类添加UpdatePrice方法StockMarket

您是否希望能够Companies从外部操纵属性StockMarket可以通过返回列表来处理,ReadOnlyCollection以便消费者无法操纵它的大小。

void Main()
{
    var sm = new StockMarket();
    sm.Companies.Add(new Company("Test", "TST", 50, 0));
    sm.UpdatePrice("Test", 45);
    var testCompany = sm.Companies.First(x => x.Name == "Test");
    Console.WriteLine($"{testCompany.Name},{testCompany.Symbol},{testCompany.Price},{testCompany.Change}");
    //Output: Test,TST,45,-5
}

class StockMarket
{
    public List<Company> Companies { get; private set; } = new List<Company>();

    public void UpdatePrice(string name, double price) {
        var index = Companies.FindIndex(x => x.Name == name);
        if(index >= 0)
        {
            var previous = Companies[index];
            Companies[index] = new Company(previous.Name, previous.Symbol, price, price - previous.Price);
        }
    }
}

class Company
{
    public Company(string name, string symbol, double price, double change) {
        Name = name;
        Symbol = symbol;
        Price = price;
        Change = change;
    }
    public string Name { get; }
    public string Symbol { get; }
    public double Price { get; }
    public double Change { get; }
    ///...
}

推荐阅读