0

I have an abstract base class with for example two subclasses. In the abstract class I use the template pattern in the "real world" but for this example lets say I have a generator method which returns the type AbstractBaseClass. Now I want the instance to be the concrete type at runtime. This is important for the template pattern as I use different implementations and hooks in each subclass.

For now I came up with the following pseudo code. I check whether this is a concrete class and call the constructor of either one of the subclasses and return the new Instance.

public abstract class AbstractBaseClass
{
    private string _someString1;
    private string _someString2;

    protected AbstractBaseClass(string someString1, string someString2)
    {
        _someString1 = someString1;
        _someString2 = someString2;
    }

    public AbstractBaseClass GenerateClass()
    {
        //...

        if(this is SubClass1)
        {
            return new SubClass1("Foo1", "Foo2");
        }

        if(this is SubClass2)
        {
            return new SubClass2("Foo3", "Foo4");
        }
        return null;
    }

    // more methods
}

public class SubClass1 : AbstractBaseClass
{
    public SubClass1(string someString1, string someString2) : base(someString1, someString2)
    { }

    // more methods
}

public class SubClass2 : AbstractBaseClass
{
    public SubClass2(string someString1, string someString2) : base(someString1, someString2)
    { }

    // more methods
}

So far so good. But now I want my code to be open for extensions but closed for modifications. I want to be able to add as many subclasses as I want, but don't have to change the generator method. How can this be achieved?

So it gets decided at runtime which instance to create. Keep in mind that I don't have an empty constructor. But there is in every case a constructor with the same signature.

I found this post. It seems to go this direction but it didn't really helped me:

Thanks!

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
BenJey
  • 127
  • 9
  • 1
    I'd consider moving that logic out into its own `AbstractBaseClassFactory` static class. – mjwills Aug 21 '18 at 11:52
  • your code is confusing and i suspect that the reason you are asking the questions you are, why are you contracting new instance in your class, via a method... – Seabizkit Aug 21 '18 at 12:20

2 Answers2

1

you should use the Factory Pattern here:

Define an interface for creating an object, but let subclasses decide which class to instantiate. The Factory method lets a class defer instantiation it uses to subclasses.

for example, though not fully qualified as Factory pattern, take a look at this snippet:

public abstract class A
{
 // Some abstract stuff

 public static A CreateInstance(Type myType)
 {
   Type type = myType; // pseudo method
   return (A)Activator.CreateInstance(type);  
 }
}

you can initialize all of your subclasses in one method.

I advice you to look deeper into Abstract Factory Design Pattern In C# in order to learn and understand the concept better.

Edit:


Or as suggested by the comment for a better generic solution:

public abstract class A
{
  // Some abstract stuff

  public static A CreateInstance<T>() where T : A, new() => new T();
}
Barr J
  • 10,636
  • 1
  • 28
  • 46
  • 1
    A generic solution: `public static A CreateInstance() where T : A, new() => new T();` – Furkan Kambay Aug 21 '18 at 12:10
  • could be a useful extension to my suggestion, I'll add it :) – Barr J Aug 21 '18 at 12:15
  • `CreateInstance` (either of your solutions) may be less useful since there are parameters being passed to the constructor. – mjwills Aug 21 '18 at 12:22
  • Thanks for the answer! Although i dont really understand the generic solution just now. How can i pass parameters to the Constructor? – BenJey Aug 21 '18 at 12:34
  • look at this: https://stackoverflow.com/questions/840261/passing-arguments-to-c-sharp-generic-new-of-templated-type – Barr J Aug 21 '18 at 12:37
  • 1
    @BenJey You can't. The `new()` is a generic type parameter constraint. This particular constraint means "`T` must have a default (parameterless) constructor" – Boris B. Aug 21 '18 at 12:48
  • Okay so if i want to pass parameters i have to use the Activator. In something like @Abdou suggested? I found the generic version quit fancy now that i fully understood it.(at least i hope so) – BenJey Aug 21 '18 at 12:56
  • 1
    @BenJey there are solutions other than Activator if you look at the answers on that question Barr mentioned. I think you should try the accepted one. Also I didn't see it but I thought of -and not sure of yet- another solution but that requires all types to be yours (source code editable) and it makes use of interfaces. If you want, I can write up an answer when I find the time (on that mentioned question, that is, since it fits there better) – Furkan Kambay Aug 21 '18 at 13:17
  • So all of the Types are mine and are editable. I also have a top level interface already over the abstract class. @FurkanKambay if you find time for the answer that you have in mind that would be awesome! Thanks a lot. Im new to Stackoverflow and already stuned by the quick and good response! – BenJey Aug 21 '18 at 13:27
  • @BenJey I just looked again and apparently someone posted a solution similar to what I was talking about, but I would make it generic: https://stackoverflow.com/a/45084549/4187549 – Furkan Kambay Aug 21 '18 at 14:04
  • Ah okay i was thinking about something similare as well. Just pass the needed parameters to the propertys of class. Wich should also be possible since they are defined as protected. Wich part do you would make generic? – BenJey Aug 21 '18 at 14:11
  • @BenJey The parameter(s) of `PopulateInstance`. In the answer, the parameter type is `object`. edit: also, maybe `return this` inside the same method to be able to chain methods. – Furkan Kambay Aug 21 '18 at 14:59
1

try this :

public AbstractBaseClass GenerateClass()
{

    return (AbstractBaseClass) Activator.CreateInstance(this.GetType(), StringParametter1,StringParametter2);
}

Good luck !

Abdou
  • 330
  • 1
  • 9