建模“我是一个*,但我也是一个**”

5
在[这篇文章]中,我正在努力实现@jonp建议的状态模式。我不太明白如何使用他发布的内容,但这让我想到也许我正在尝试将方形钉子塞进圆孔里。所以我的问题是:
如果我网站上的访问者可以扮演多个角色,即User可以是VendorEmployerAdvertiser或以上所有角色,那么我应该使用继承吗?我已经声明了:
class Vendor : User {}
class Advertiser : User {}

等等,但是当用户既是供应商又是雇主时,不同类的实例实际上指向同一个基础对象...我不确定这能行。我该怎么建模?

* 更新 *

谢谢大家(你们都获得了一分(这是我能给的最多的))。在过去的几天里,我一直为EF的深层复制、向下转型和状态模式而苦恼。角色方法更加合理。


2
也许我可以引起你对C++的兴趣,这是一种具有任意继承的编程语言。;-) - Kerrek SB
@Kerrek C++并没有提供太多帮助来动态创建一个独特的用户子类型,该子类型继承了基于从数据库中读取的角色的动态集合。 - Jeffrey Hantin
4个回答

4
这听起来像是非常适合使用属性模式(或我称之为属性模式)的情况。这是一种比简单继承更松耦合的方法,可用于指定多个“行为”或在您的情况下“用户”种类。它实际上就是一个对象具有另一种对象的标记而已。
最简单的实现方式是拥有一个具体的User类,其中包含只读属性IList<UserRole>(内部可能是List<T>字段)。然后,您的UserRole类将是抽象的,并且VendorRole/AdvertiserRole/等等会从中派生,允许您在给定用户上标记任意数量的不同角色(甚至是同一类型的角色)。这些角色还可以定义自己的自定义行为、实用程序方法等。
此外,您可以在User类上定义一个GetRole<TRole>方法,以便方便地访问特定类型的角色(假设每个User仅具有特定子类型的单个Role)。
顺便说一句:您还可以考虑装饰者模式,它与上述模式密切相关,但个人认为这在这里有点过度设计了,并没有增加灵活性或强大的功能。它通常只是混淆了您要做的事情,但是请随意探索。

这是一个装饰器吗?在我看来更像是一个标准的多态集合场景,或者可能是多个1:0-1引用。 - Jeffrey Hantin
@Jeffrey:你说得对,这不完全是装饰器模式,但这是一种更明智/更简单的方法,在这里同样强大。 :-) - Noldorin
其实我刚刚更新了我的回答... 我注意到装饰器模式显然是密切相关的,但稍微复杂一些。 - Noldorin
我曾经听说过这被称为能力模式。基本上,它允许你的对象通过直接实现它们或返回一个实现它们的成员,在多个接口下进行操作。 - Ryan Gross
@Ryan:好知道。不过我还是喜欢我的名字……更加透明。;) - Noldorin
@Ryan 能力模式?... (阅读页面) ... 哦,你的意思是Java团队发现了除了在C中实现对象之外还有http://goo.gl/zm2qf的用处。(我在使用Java 1.1到1.4之后决定他们试图复制C#而失去了重点。) - Jeffrey Hantin

3
如果不同角色需要包含使用多态和抽象方法实现的不同逻辑,那么你应该偏爱组合而非继承。例如:
public class User
{
    public Role Role { get; set; }
}

public abstract class Role
{
    abstract void DoRoleSpecificStuff();
}

public class Vendor : Role
{
    public void DoRoleSpecificStuff()
    {
        /* ... */
    }
}

public class Employer : Role
{
    public void DoRoleSpecificStuff()
    {
        /* ... */
    }
}

public class Advertiser : Role
{
    public void DoRoleSpecificStuff()
    {
        /* ... */
    }
}

如果用户可以拥有多个角色,则考虑使用角色集合属性:
public IEnumerable<Role> Roles { get; set; }

否则,使用带有[Flags]属性的枚举也可以,具体取决于您是否需要能够分配多个角色:
public class User
{
    public Roles Roles { get; set; }
}

[Flags]
public enum Roles
{
    Advertiser = 0x0,
    Employer = 0x1,
    Vendor = 0x2      
}

您需要按照以下方式分配不同的角色:

您需要按照以下方式分配不同的角色:

User user = new User
{
    Roles = Roles.Advertiser | Roles.Vendor;
};

那会让用户既成为广告商又成为供应商,但不是雇主。

1
嗯,不是的。原帖问的是用户可能有多个角色的情况。 - Jeffrey Hantin
2
你不能使用User.Roles吗?Roles是IEnumerable<Role>类型的吗? - Mathias
@Jeffrey,请您检查一下您的评论。当您输入时,我正在更新我的答案。 - Marius Schulz
@Mathias:当然,这取决于您是否需要能够分配多个角色。我的更新答案也包含了这种可能性。 - Marius Schulz
@Marius,[flags] 的东西真的很酷。虽然它对我目前的问题没有帮助,但我一定要记住它! - ekkis

2

在这里确实是采用了组合优于继承的方式,但如果一个用户可以具有多个角色,则更像是这样。

如果角色较少,类似于外连接结果的“停车场”可能会起作用。 在这种模式下,不需要Role基类。

class User
{
    // all of these may be null if not applicable
    VendorRole VendorRole { get; set; }
    EmployeeRole EmployeeRole { get; set; }
    AdvertiserRole AdvertiserRole { get; set; }
}

如果用户可能有多个单一角色的实例,会弹出一个集合:
class User
{
    // all of these may be null if not applicable
    VendorRole VendorRole { get; set; }
    EmployeeRole EmployeeRole { get; set; }
    ICollection<AdvertiserRole> AdvertiserRoles { get; }
}

另外,如果角色存在混乱的堆积,如果角色是动态添加的,或者其他情况,您需要一个集合和一个基本类型。但是,如果涉及实体框架,我认为动态添加角色似乎不太可能。

class User
{
    ICollection<Role> Roles;
}

2

“我是一个*,但我也是一个**”被称为多重继承。C#不支持此功能,因此您不应考虑它。


4
也许是这样,但建立他所询问的“概念”并不需要使用机器智能。 - Jeffrey Hantin
嗯...我不知道。MI对于建模上升很有用,即当分类树上的节点属于多个根时,但是(我可能错了),在我的情况下继承是横向的;换句话说,员工和供应商实际上是分类树中的兄弟。 - ekkis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接