C# 如何在解耦对象之间实施约束?

C# 如何在解耦对象之间实施约束?,c#,events,decoupling,intercept,C#,Events,Decoupling,Intercept,注意-我将原来的帖子移到了底部,因为我认为它对这个帖子的新手仍然有价值。下面是根据反馈改写问题的尝试 完全编辑的帖子 好的,我将试着详细阐述一下我的具体问题。我意识到我将域逻辑与接口/表示逻辑混合了一点,但老实说,我不确定在哪里将其分开。请容忍我:) 我正在编写一个应用程序,该应用程序(除其他外)执行物流模拟以移动物品。其基本思想是,用户可以看到一个类似于Visual Studio的项目,在该项目中,她可以添加、删除、命名、组织、注释等我将要概述的各种对象: 项和位置是基本的无行为数据项 cl

注意-我将原来的帖子移到了底部,因为我认为它对这个帖子的新手仍然有价值。下面是根据反馈改写问题的尝试

完全编辑的帖子 好的,我将试着详细阐述一下我的具体问题。我意识到我将域逻辑与接口/表示逻辑混合了一点,但老实说,我不确定在哪里将其分开。请容忍我:)

我正在编写一个应用程序,该应用程序(除其他外)执行物流模拟以移动物品。其基本思想是,用户可以看到一个类似于Visual Studio的项目,在该项目中,她可以添加、删除、命名、组织、注释等我将要概述的各种对象:

  • 位置是基本的无行为数据项

    class Item { ... }
    
    class Location { ... }
    
  • WorldState是项目位置对的集合。WorldState是可变的:用户可以添加和删除项目,或更改其位置

    class WorldState : ICollection<Tuple<Item,Location>> { }
    
其基本思想是,用户可以创建这些对象,将它们连接在一起,并可能重新使用它们。一个WorldState可由多个平面对象使用。然后可以在多个计划上运行模拟

冒着极其冗长的风险,举个例子

var bicycle = new Item();
var surfboard = new Item();
var football = new Item();
var hat = new Item();

var myHouse = new Location();
var theBeach = new Location();
var thePark = new Location();

var stuffAtMyHouse = new WorldState( new Dictionary<Item, Location>() {
    { hat, myHouse },
    { bicycle, myHouse },
    { surfboard, myHouse },
    { football, myHouse },
};

var gotoTheBeach = new Plan(StartState: stuffAtMyHouse , Plan : new [] { 
    new [] { surfboard, theBeach, 1/1/2010 10AM }, // go surfing
    new [] { surfboard, myHouse, 1/1/2010 5PM }, // come home
});

var gotoThePark = new Plan(StartState: stuffAtMyHouse , Plan : new [] { 
    new [] { football, thePark, 1/1/2010 10AM }, // play footy in the park
    new [] { football, myHouse, 1/1/2010 5PM }, // come home
});

var bigDayOut = new Plan(StartState: stuffAtMyHouse , Plan : new [] { 
    new [] { bicycle, theBeach, 1/1/2010 10AM },  // cycle to the beach to go surfing
    new [] { surfboard, theBeach, 1/1/2010 10AM },  
    new [] { bicycle, thePark, 1/1/2010 1PM },  // stop by park on way home
    new [] { surfboard, thePark, 1/1/2010 1PM },
    new [] { bicycle, myHouse, 1/1/2010 1PM },  // head home
    new [] { surfboard, myHouse, 1/1/2010 1PM },

});

var s1 = new Simulation(...);
var s2 = new Simulation(...);
var s3 = new Simulation(...);

IEnumerable<SimulationResult> results = 
    from simulation in new[] {s1, s2}
    from plan in new[] {gotoTheBeach, gotoThePark, bigDayOut}
    select simulation.Execute(plan);
因此,基本上,当用户试图通过
world.removietem(item)
调用从WorldState(可能是整个项目)中删除一个项时,我希望确保在使用该WorldState的任何计划对象中都不会引用该项。如果是,我想告诉用户“嘿!下面的Plan X正在使用此项!在尝试删除它之前,先处理它!”。我确实不想从
世界中得到的那种行为。RemoveItem(item)
调用是:

  • 正在删除该项,但仍有计划引用该项
  • 删除项目,但让计划以静默方式删除其列表中引用该项目的所有元素。(事实上,这可能是可取的,但只是作为第二选择)
因此,我的问题基本上是如何以一种干净的解耦方式实现这种期望的行为。我曾考虑将此作为用户界面的权限(因此,当用户在项目上按“del”时,它会触发对计划对象的扫描,并在调用world.removietem(项目)之前执行检查)-但是(a)我还允许用户编写和执行自定义脚本,以便他们可以调用
world.removietem(项目)
自己,以及(b)我不相信这种行为是纯粹的“用户界面”问题

呸。我希望有人还在读

原创帖子 假设我有以下几个类:

public class Starport
{
    public string Name { get; set; }
    public double MaximumShipSize { get; set; }
}

public class Spaceship
{
    public readonly double Size;
    public Starport Home;
}
因此,假设存在一个约束条件,即宇宙飞船的大小必须小于或等于其所在地的最大飞船大小

那么我们该如何应对呢

传统上我做过这样的事情:

partial class Starport
{
    public HashSet<Spaceship> ShipsCallingMeHome; // assume this gets maintained properly
    private double _maximumShipSize;
    public double MaximumShipSize
    {
        get { return _maximumShipSize; } 
        set
        {
            if (value == _maximumShipSize) return;
            foreach (var ship in ShipsCallingMeHome)
                if (value > ship)
                    throw new ArgumentException();
            _maximumShipSize = value
        }
    }
}
interface ISpacebaseInterceptor<T>
{ 
    bool RequestChange(T newValue); 
    void NotifyChange(T newValue); 
} 
但我不确定这是否更好。我也不确定以这种方式滚动我自己的事件是否会对性能产生某些影响,或者这可能是一个好主意/坏主意的其他原因

  • 第三种选择可能是使用PostSharp或IoC/依赖项注入容器的一些非常古怪的aop。我还没准备好走那条路

  • 管理所有支票等的上帝对象-只需搜索stackoverflow就给我的印象是这是坏的和错误的

  • 我主要担心的是,这似乎是一个相当明显的问题,我认为这是一个相当常见的问题,但我没有看到任何关于它的讨论(例如,System.ComponentModel没有提供否决财产变更事件的设施,是吗?);这让我担心我(再次)未能掌握耦合或(更糟糕的)面向对象设计中的一些基本概念

    评论?
    }你知道宇宙飞船必须有一定的尺寸;将大小放在基类中,并在那里的访问器中实现验证检查


    我知道这似乎过于关注您的具体实现,但这里的要点是您的期望并不像您期望的那样解耦;如果您在基类中对派生类中的某些内容有一个严格的期望,那么您的基类正在对提供该类实现的派生类做出一个基本的期望;也可以直接将该期望迁移到基类,在这里你可以更好地管理约束。

    < P>你可以做一些像C++ STL特性类的事情——实现一个通用的<代码> SpaceBase < /C> >,它有两个参数化<代码>类型< /Calp>S-一个,定义了<代码>飞船/<代码>成员,另一个约束
    SpaceBase
    及其
    SpaceShip
    的方法是使用
    SpaceBaseTraits
    类来封装基地的特性,例如它可以包含的对飞船的限制。

    InotifyPropertyChange接口是为数据绑定而设计的,这就解释了为什么它没有你想要的能力。我可以试试这样的东西:

    partial class Starport
    {
        public HashSet<Spaceship> ShipsCallingMeHome; // assume this gets maintained properly
        private double _maximumShipSize;
        public double MaximumShipSize
        {
            get { return _maximumShipSize; } 
            set
            {
                if (value == _maximumShipSize) return;
                foreach (var ship in ShipsCallingMeHome)
                    if (value > ship)
                        throw new ArgumentException();
                _maximumShipSize = value
            }
        }
    }
    
    interface ISpacebaseInterceptor<T>
    { 
        bool RequestChange(T newValue); 
        void NotifyChange(T newValue); 
    } 
    
    接口ISpacebaseInterceptor
    { 
    bool-RequestChange(T-newValue);
    变更无效(T新值);
    } 
    
    根据修订后的问题:

    我认为
    WorldState
    类需要一个委托。。。而
    Plan
    将设置一个方法,该方法应被调用以测试项目是否正在使用。诸如此类:

    delegate bool IsUsedDelegate(Item Item);
    
    public class WorldState {
    
        public IsUsedDelegate CheckIsUsed;
    
        public bool RemoveItem(Item item) {
    
            if (CheckIsUsed != null) {
                foreach (IsUsedDelegate checkDelegate in CheckIsUsed.GetInvocationList()) {
                    if (checkDelegate(item)) {
                        return false;  // or throw exception
                    }
                }
            }
    
            //  Remove the item
    
            return true;
        }
    
    }
    
    然后,在计划的构造函数中,设置要调用的委托

    public class plan {
    
        public plan(WorldState state) {
            state.IsUsedDelegate += CheckForItemUse;
        }
    
        public bool CheckForItemUse(Item item) {
             // Am I using it?
        }
    
    }
    
    这是非常粗糙的,当然,我会试着在午餐后补充更多:)但是你明白了

    (午餐后:) 缺点是您必须依靠
    计划
    来设置代理。。。但这是无法避免的。一个
    项目
    无法告诉它有多少引用,或者
    delegate bool IsUsedDelegate(Item Item);
    
    public class WorldState {
    
        public IsUsedDelegate CheckIsUsed;
    
        public bool RemoveItem(Item item) {
    
            if (CheckIsUsed != null) {
                foreach (IsUsedDelegate checkDelegate in CheckIsUsed.GetInvocationList()) {
                    if (checkDelegate(item)) {
                        return false;  // or throw exception
                    }
                }
            }
    
            //  Remove the item
    
            return true;
        }
    
    }
    
    public class plan {
    
        public plan(WorldState state) {
            state.IsUsedDelegate += CheckForItemUse;
        }
    
        public bool CheckForItemUse(Item item) {
             // Am I using it?
        }
    
    }
    
    public class Starport
    {
        public string Name { get; protected set; }
        public double MaximumShipSize { get; protected set; }
    
        public AircarfDispatcher GetDispatcherOnDuty() {
            return new AircarfDispatcher(this); // It can be decoupled further, just example
        }
    }
    
    public class Spaceship
    {
        public double Size { get; private set; };
        public Starport Home {get; protected set;};
    }
    
    public class AircarfDispatcher
    {
        Startport readonly airBase;
        public AircarfDispatcher(Starport airBase) { this.airBase = airBase; }
    
        public bool CanLand(Spaceship ship) {
            if (ship.Size > airBase.MaximumShipSize)
                return false;
            return true;
        }
    
        public bool CanTakeOff(Spaceship ship) {
            return true;
        }
    
        public bool Land(Spaceship ship) {
            var canLand = CanLand(ship);
            if (!canLand)
                throw new ShipLandingException(airBase, this, ship, "Not allowed to land");
            // Do something with the capacity of Starport
        }
    
    }
    
    
    // Try to land my ship to the first available port
    var ports = GetPorts();
    var onDuty = ports.Select(p => p.GetDispatcherOnDuty())
        .Where(d => d.CanLand(myShip)).First();
    onDuty.Land(myShip);
    
    // try to resize! But NO we cannot do that (setter is protected)
    // because it is not the responsibility of the Port, but a building company :)
    ports.First().MaximumShipSize = ports.First().MaximumShipSize / 2.0