简体   繁体   中英

Pattern for a list that can contain two types of items

I'm trying to figure out a nice way to adopt an API change we've recently undergone that "weakens" a type on one of our objects.

Users used to be able to provide a list of references to "Dogs". We've made a decision that we now accept "Dogs" and "Cats".

The client library uses strongly typed references, so the property currently looks like:

public List<IReference<Dog>> occupants { get; set; }

I considered changing it to:

public List<IReference<Animal>> occupants { get; set; }

But I don't like that what used to be compile-time validation is now run-time (and server-side) validation. I would have preferred to have something more strongly typed like:

public List<Occupant> occupants { get; set; }

public class Occupant : IReference<Animal> {
    IReference<Animal> _internalReference;
    public Occupant(IReference<Dog> dog) { _internalReference = dog }
    public Occupant(IReference<Cat> cat) { _internalReference = cat }
    //... (Implement IReference<Animal> as a pass-through to _internalReference)
}

Unfortunately, because C# does not allow implicit operators to convert from an interface, existing users updating their client library would have to undergo hundreds of lines of code changes to "wrap" all their existing Dog references in an Occupant constructor.

So next I considered creating a new class for the list with some helper methods:

public class OccupantList : List<Occupant> { 
    public void Add(IReference<Dog> newDog) => base.Add(new Occupant(newDog));
    public void Add(IReference<Cat> newCat) => base.Add(new Occupant(newCat));
    // For backwards compatibility with new list assignments
    public static implicit operator OccupantList(List<IReference<Dog>> legacy) =>
        new OccupantList(legacy.Select(dog => new Occupant(dog)));
}

Unfortunately, C# exposes no way to override the core ICollection.Add implementation behind the scenes, which is used by all sorts of reflection utilities and my JSON serializer to populate this list, so it complains when it's given objects to add that aren't already of the type Occupant . I personally encountered issues with the JSON deserializer I was using. I could write a custom deserializer for this class but I took it as a sign that trying to inherit List<Occupant> and add support for types that don't inherit from Occupant was a bad idea doomed for failure.

Does anyone have any ideas or examples that can might steer us in the right direction?


This is only "my problem"

The real kicker is that this isn't even a backwards incompatibility at the API layer. The API uses dynamic typing (JSON with a Python interpreter) so the only change is that the following JSON:

{ "occupants": [{"ref_id":"abc123"}, {"ref_id":"zzz999"}] }

becomes:

{ "occupants": [{"ref_id":"abc123", "ref_type": "Dog"}, {"ref_id":"gdr736", "ref_type":"Cat"}] }

With a backwards-compatibility rules that treats the reference as a "Dog" if no type is specified.

Unfortunately, because I made the early decision to add compile-time typing to these reference objects, the C# client library users are the only ones suffering from the type weakening, whereas the API is still able to figure out the appropriate type dynamically at runtime.

At worst:

You're going to hate this but if that API has any consumers the best practice suggests that your breaking changes means it's now time to version your API.

At best:

I don't see the signature of your API controller mentioned but if the model in question is defined as a parameter of that method you might be able to get away with overloading the controller method, letting the model binder handle the different shape, and then resolving the problem in some kind of middleware or automapper.

Why not solve with a change in the model?

Because it sounds like you've already released this API for some kind of external consumption you really really shouldn't break the contract described by that input model.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM