简体   繁体   中英

Generic Identifier handling implementations

Since my application has to work with a lot of identifiers, I thought about abstracting them using a Id<T> class. This class would work like a Guid, it has a CreateNew() method and so on, but would create a different Id depending on the type of T.

Examples:

var id = Id<Individual>.CreateNew(); // == "INDIVIDUAL-12345"
var id = Id<Organization>.CreateNew(); // == "ORGANIZATION-67890"

Now, Individual and Organization implement the interface IUser .

Now I would like my Id<> to handle different implementations, but this does not work.

ie consider the following class, using Ids:

class Transaction{
    private Id<IUser> _sender;
    private Id<IUser> _receiver;
    private Amount _amount;

    public Transaction(Id<IUser> sender, Id<IUser> receiver, Amount amount {...}
}

I would like to be able to call:

new Transaction(Id<Individual>.CreateNew(), Id<Organization>.CreateNew(), new Amount());

but this is not possible as the constructor requires the types to be Id<IUser> . Can you think about any workaround?


Update

Thanks for the answers until now.
Id<IUser> is just an example (and the content of IUser is not that important here), I'd like to support identifiers for many objects (10/15, more in the future?).

Id<T> uses a static class as helper, called TypesMatcher, whose job is to translate a type to a prefix (ie "Individual", "Organization", "Transaction"..).

Id<T>.CreateNew() looks more or less like this:

    [Pure]
    public static Id<T> CreateNew()
    {
        if (!TypesMatcher.IsSupported<T>()) throw new Exception($"Type {typeof(T)} is not supported.");

        string prefix = TypesMatcher.GetPrefixForType<T>();
        string body = Guid.NewGuid().ToString();

        return new Id<T>($"{prefix}-{body}");
    }

I'd start by looking into your desired syntax: Id<Individual>.CreateNew() .

It means there should be an Id<T> class and have a static CreateNew method.

The static method cannot be overridable, so a good way to make it extensible, is injecting the behavior inside the method.

So for each entity type we can create a class like SomeEntityId deriving from Id<SomeEntity> . The class should contain the custom logic for generating the id. Then we can register them in a DI container. Then in the static CreateNew method, I'd get instance of the registered classes based on T from the container. This way you can inject the custom logic based on T .

I'll also change the Transaction class to rely on IId<IUser> , this way I can create a Variant Generic Interface like IId<T> .

Example

public interface IId<out T> where T : IUser
{
    string Value { get; }
}
public class Id<T> : IId<T> where T : IUser
{
    public string Value { get; protected set; }
    protected virtual Id<T> GenerateId() => new Id<T>() { Value = $"{Guid.NewGuid()}" };
    public static Id<T> CreateNew() => Container.GetInstance<Id<T>>().GenerateId();
}
public class IndividualId : Id<Individual>
{
    protected override Id<Individual> GenerateId()
    {
        var instance = new IndividualId();
        instance.Value = $"IndividualId-{Guid.NewGuid()}"; //Example of custom logic
        return instance;
    }
}
public class Transaction
{
    private IId<IUser> _sender;
    private IId<IUser> _receiver;
    public Transaction(IId<IUser> sender, IId<IUser> receiver) { }
}

For each type which you have custom logic, make sure you register the type instance for Id<yourtype> , for example register IndividualId for Id<Individual> in the container.

Then for usage, you can use this:

new Transaction(Id<Individual>.CreateNew(), Id<Organization>.CreateNew());

I am not sure I am getting your goal/the problem you are trying to solve here.

Generics are about allowing a person that is using your code to put in any class, without breaking compile time type checks . Prior to generics, we had to either make a special collection for every type or use Object as the type. One was a unsuiteable amount of work with tons of pitfalls. The other just broke type safety. It is just a mess we mostly prefer to stay burried in pre-generics history. Still I feel it is important to understand why Generics exist, what they are about solving and what they can not solve.

As you use a Interface function to access the values, they can only be resolved at runtime. While they might be a compile time constant in the class, there is no way for the compiler to ever be sure of it as they are handed out by a function at runtime. However it seems possible to add a sort-of-compile time Constant to Interfaces, with the Literal Keyword . But I am unsure if Implementers of that interface can add a tiffrent "typeID" value. I just read about this being a thing, while writing.

Personally that code you shared reminds me a lot of a variant of the Factory/not quite singleton pattern. A factory method where the same input (the same Collection, Type) will result in the same output. But I can not find a example right now.

With the shared code, the only thing I can see being made generic is the transaction Method:

public <senderType, ReceiverType> Transaction (senderType sender, ReceiverType receiver, Amount amount) { }

But again, I am unsure what you tried to do and particular the problem you tried to solve.

Generics cannot handle any idea of inheritance. Covariance is only available on Interfaces.

If you have a covariant interface

  interface IID<out IUser>
  {
        IID<IUser> CreateNew();
  }

And you use this instead of ID<User> , and Individual and Organization implement IUser , and ID<Invdidual> implements IID<Individual> and ID<Organization> implements IID<Organization> .

Then you can make this assigment:

  IID<IUser> id = = ID<Individual>.CreateNew();

or use it as a Parameter.

But on the other hand side, to use it in this way:

  IID<Individual> id =  (IID<Individual>)ID<Individual>.CreateNew();

you would need a cast.

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