简体   繁体   中英

Creating an instance of derived class through the base class without hardcoding

My problem is as follows:

I have a base class that needs to be abstract. It has several derived classes, each with their own special properties that are contained in the Properties member.

I need to be able to create a new instance of one of these derived classes, so that all the members are equivalent but modifying the new instance doesn't modify the original.

Finally, I want to do it without having to hardcode in every derived type of the base class. (Which would, admittedly, be the easiest solution, however that isn't the point)

All the derived classes satisfy an "is-a" relationship to the base class.

Here is the code:

public abstract class BaseClass
{
    //Default properties here
    int x, y, z, ...;

    //Custom made class to hold custom properties
    protected Attributes Properties;

    public BaseClass createNewInstance()
    {
        return createNewInstanceStep1();
    }

    //Each derived class implements their own version of this,
    //to handle copying any custom members contained in Properties.
    protected abstract BaseClass createNewInstanceStep2();

    protected BaseClass createNewInstanceStep1()
    {
        BaseClass newInstance = new BaseClass(); // <- Doesn't work because class is abstract

        //Copy default properties
        newInstance.x = x;
        newInstance.y = y;
        newInstance.z = z;

        //Call the new instance's step 2 method, and return the result.
        return newInstance.createNewInstanceStep2();
    }
}

The issue with this code is the BaseClass newKeyFrame = new BaseClass(); line. As the class is abstract, you cannot create an instance of it.

The problem is that I need to be able to call the constructor of whatever type the derived class is, as they all have different code in their constructors that cannot be shared.

I've heard that using Reflection might be a viable solution, however I have no idea how.

How can I solve this without having to hardcode in a case for every derived type?

You could make createNewInstanceStep1 generic. I've also modified the Step2 to be type void (I'm expecting it to modify the current instance, so the return would always be return this; anyway), because otherwise it doesn't really make sense the way I'd like to use it here. If it doesn't make sense to change it like this, then my whole approach of only making this method generic won't work.

And createNewInstance now uses reflection to call the equivalent of return createNewInstanceStep1<this.GetType()>(); .

public BaseClass createNewInstance()
{
    var method = typeof(BaseClass).GetMethod("createNewInstanceStep1", BindingFlags.NonPublic | BindingFlags.Instance).MakeGenericMethod(this.GetType());
    var value = method.Invoke(this, null);
    return (BaseClass)value;
}

//Each derived class implements their own version of this,
//to handle copying any custom members contained in Properties.
protected abstract void createNewInstanceStep2();

protected T createNewInstanceStep1<T>() where T : BaseClass, new()
{
    T newInstance = new T(); // works!

    //Copy default properties
    newInstance.x = x;
    newInstance.y = y;
    newInstance.z = z;

    //Call the new instance's step 2 method, and return the result.
    newInstance.createNewInstanceStep2();
    return newInstance;
}

If this won't work, another approach is a self-referential generic type . It's good to avoid this, though, because it's confusing and overall not a good design.

public sealed class SubClass : BaseClass<SubClass>
{
    protected override SubClass createNewInstanceStep2()
    {
        Console.WriteLine("In step 2");
        return this;
    }
}
public abstract class BaseClass<T> where T : BaseClass<T>, new()
    public T createNewInstance()
    {
        return createNewInstanceStep1();
    }

    //Each derived class implements their own version of this,
    //to handle copying any custom members contained in Properties.
    protected abstract T createNewInstanceStep2();

    protected T createNewInstanceStep1()
    {
        T newInstance = new T();
        ...

What's the problem with letting the derived classes do the instantiation of the new instance?

public abstract class BaseClass
{
    //Default properties here
    int x, y, z, ...;

    //Custom made class to hold custom properties
    protected Attributes Properties;

    public abstract BaseClass createNewInstance();

    protected void CopyData(BaseClass original)
    {
        this.x = original.x;
        this.y = original.y;
        this.z = original.z;
    }
}

public class ChildClass : BaseClass
{
    public BaseClass createNewInstance()
    {
        ChildClass newInstance = new ChildClass();
        newInstance.CopyData(this);

        // Do any additional copying
        return newInstance;
    }
}

您可以使createNewInstanceStep1成为受保护的void init方法,该方法启动此对象,并在每个子类的构造函数中调用它。

How are you going to know what type of instance to create? If you'll have an existing instance of the derived class and want to create another instance which is basically identical to it, you should have your base type implement a protected virtual CloneBase method which calls MemberwiseClone and does any deep copying the base type knows about, and a public Clone method which chains to CloneBase and casts to the base type. Each derived types should override CloneBase to chain to base.CloneBase and add any necessary additional deep copying (that step may be omitted if no additional deep-copy logic is required), and also shadow the public Clone method with one that chains to CloneBase and casts the result to its type [using a separate CloneBase makes it possible to both declare a new Clone method with a different signature while also overriding and chaining to the base-class method].

If you'll have an existing instance of the new class, but want its properties to be copied from some other instance, could have an abstract ConstructInstanceLike(x) method which each derived type would implement to either call one of its constructors, or clone itself and modify the clone to match the passed-in object. Neither approach is terribly elegant, but either can work.

If you won't have an existing instance of the new class, you'll need some other means of getting something of the appropriate type. The nicest approach is probably to store a collection of Func<TParams, TResult> delegates, one for each derived type of interest, and then invoke one of those functions to generate an object of one of the associated derived type. It would also be possible to define an interface

IFactory<TParam, TResult> { TResult Create(TParams param); }

but in many cases a delegate will be more convenient to work with.

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