Modelling “I'm a * but I'm also a **”

2019-04-29 21:22发布

问题:

In [this post], I'm struggling to implement a State Pattern as @jonp suggests. I don't quite get how to use what's he's posted but it leads to the thought that maybe I'm trying to fit a square peg into a round hole. So my question:

If I have a visitor to my site that can play multiple roles i.e. a User could be a Vendor, an Employer, an Advertiser, OR all of the above, should I be using inheritance? I've declared:

class Vendor : User {}
class Advertiser : User {}

et cetera, but when a user is both a vendor and an employer then instances of different classes really point to the same underlying object... I'm not sure this can work. How do I model it?

* update *

thanks everyone (you all get a point (it's all I can give)). I've been pulling my hair out over deep-copies with EF, downcasting and the state pattern for the last several days. The role approach makes much more sense.

回答1:

This sounds like a situation to which the attribute pattern (or so I call it) would be very appropriate. It's a much more loosely-coupled approach than simple inheritance that can be used to specify multiple "behaviours" or in your case kinds of User. It's really nothing more complicated than an object having tags of another kind of object.

The easiest way to implement it would be to have a concrete User class, with a read-only property IList<UserRole> (internally this can be a List<T> field perhaps). Your UserRole class would then be abstract, and VendorRole/AdvertiserRole/etc. would derive from it, allowing you to tag on an arbitrary number of different roles (even ones of the same type) onto a given user. These roles can in addition define their own custom behaviours, utility methods, etc.

In addition, you could define a GetRole<TRole> method on your User class to facilitate access to roles of a specific type (assuming each User only has a single Role of a specific subtype).

Side note: you may also consider the decorator patern, which is closely related to the above mentioned pattern -- though personally I feel it is overkill here, and really adds nothing in terms of flexibility or power. It often just obscures what you're trying to do; though feel free to investigate anyway.



回答2:

You should favor Composition over Inheritance if the different roles have to contain different logic that would be implemented using polymorphism and abstract methods, for example:

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()
    {
        /* ... */
    }
}

If a User can have multiple Roles, consider using a Roles collection property:

public IEnumerable<Role> Roles { get; set; }

Otherwise, an enumeration using the [Flags] attribute could be fine, too, depending on whether you need to be able to assign multiple Roles:

public class User
{
    public Roles Roles { get; set; }
}

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

You would assign a combination of different roles as follows:

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

That would make the User both an Advertiser and a Vendor, but not an Employer.



回答3:

“I'm a * but I'm also a **” is known as Multiple Inheritance. C# does not support this, so you shouldn't be considering it.



回答4:

It's indeed composition over inheritance here, but it's more like this if a single user can have multiple roles.

If there are relatively few roles, a 'parking lot' analogous to an outer join result may work. In this pattern, no Role base class is required.

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

If a user may have multiple instances of a single role, a collection pops up:

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

Alternatively, if there may be a messy pile of roles, if roles get added dynamically, or what have you, you'll need a collection and a base type. If Entity Framework is involved, though, dynamically added roles seem unlikely to me.

class User
{
    ICollection<Role> Roles;
}