如何共享对象列表而无法修改其状态?

时间:2018-05-10 19:26:01

标签: c#

我们说我有一个 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能够改变公司的状态。

那么在请求时使用表单分享公司状态的最佳方式是什么,并阻止表单进行修改。

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

感谢任何帮助!

5 个答案:

答案 0 :(得分:1)

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

这是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; }
    ///...
}

答案 1 :(得分:1)

这将是一个解决方案:

在StockMarket类中创建Company类作为私有内部类,这样它只能在其内部访问,然后提供仅包含所有属性的获取并使公司实现的接口它。您必须使StockMarket公司列表成为界面类型。

您必须通过将界面的List对象强制转换为原始类类型来进行任何修改。

示例:

class Program
{
    public static StockMarket stockMarket = new StockMarket();

    static void Main(string[] args)
    {

    }
}

public interface ICompany
{
    string Name { get; }
}

public class StockMarket
{
    public StockMarket()
    {
        Companies = SomeWildFunctionThatRetrievesAllCompanies();
    }

    public void OneWildFunctionThatModifiesACompany()
    {
        Company dunno = (Company)Companies[0];
        dunno.Name = "Modification Made Possible";
    }

    private List<ICompany> SomeWildFunctionThatRetrievesAllCompanies()
    {
        return new List<ICompany>(new List<Company>());
    }

    public List<ICompany> Companies { get; private set; }

    private class Company : ICompany
    {
        public string Name { get; set; }
    }
}

答案 2 :(得分:0)

试试这个:

class Company
{
    public Company(Type type,string name,string symbol,double price, double change)
    {
        if (type.Name == "StockMarket")
        {
            Name = name;
            Symbol = symbol;
            Price = price;
            Change = change;
        }

    }
    private string Name { get; set;  }
    private string Symbol { get;  set; }
    private double Price { get; set; }
    private double Change { get; set; }
    ///...
}

这将允许您仅在类型为StockMarket时更改状态 像:

class StockMarket
{
    public List<Company> Companies { get; set; }

    public StockMarket()
    {
        Companies = new List<Company>();

    }
    public StockMarket someMethod()
    {

        //You can change the state here
        StockMarket s = new StockMarket();
        s.Companies.Add(new Company(this.GetType(), "aa", "_", 123, 1234));
        return s;
    }
    //...
}

现在你无法改变这里的状态:

public partial class Observer: Form
{
    private StockMarket stockMarket;

    public ConcreteObserverPull()
    {
       InitializeComponent();
       stockMarket = new StockMarket();
     //Here you cannot change the state
       stockMarket.Companies.Add(new Company(this.GetType(), "aa", "_", 123,12)); 


    }
    //...
}

答案 3 :(得分:-1)

抱歉,我不知道C#,但是作为一个想法,你可以用装饰器或代理包装返回的实体,这会在尝试修改公司状态时抛出异常。

答案 4 :(得分:-3)

返回字段设置为只读的克隆是最安全的方法。