12

I have a Factory. I do not want to allow classes that this factory produces to be instantiated outside of the factory. If I make them abstract, static, or give them private constructors then they won't be instantiable at all! Is this a language restriction or what?

I don't want to allow this

var awcrap = new Extrude2013 (); // BAD !!!
awcrap.extrudify (); // I don't want to allow this

Rest of code:

using System;

namespace testie
{
    public enum ExtrudeType { Extrude2013,  Extrude2014 }

    public interface IExtrudeStuff {
        void extrudify();
    }

    public class Extrude2013 : IExtrudeStuff { 
        public void extrudify(){ 
            Console.WriteLine ("extrudify 2013");
        }
    }

    public class Extrude2014 : IExtrudeStuff { 
        public void extrudify(){ 
            Console.WriteLine ("extrudify 2014");
        }
    }
    public static class ExtrudeFactory {
        public static IExtrudeStuff Create(ExtrudeType t) {
            switch (t) {
                case ExtrudeType.Extrude2013: return new Extrude2013 ();
                case ExtrudeType.Extrude2014: return new Extrude2014 ();
                default: return null; 
            } 
        }
    }

    class MainClass {
        public static void Main (string[] args) {
            // Now for the pretty API part
            var o = ExtrudeFactory.Create (ExtrudeType.Extrude2013);
            o.extrudify ();
            var p = ExtrudeFactory.Create (ExtrudeType.Extrude2014);
            p.extrudify ();

            var awcrap = new Extrude2013 (); // BAD !!!
            awcrap.extrudify (); // I don't want to allow this
        }
    }
}
The Internet
  • 7,959
  • 10
  • 54
  • 89

2 Answers2

7

You can't completely disallow this. Whether or not it's a language "restriction" would be a matter of opinion, but there are things that you could consider:

  • Make the constructor internal. This will allow any type within the declaring assembly to call the constructor, but nothing outside the assembly. This would mean that any code you write in that assembly to be responsible for calling the factory, and it also means that you could not declare subtypes of the class in another assembly, since it would be unable to call the constructor.
  • A similar approach would be to make the class you expose abstract (or an interface), then declare an internal (or even private as a subclass of the factory, since it would never be referenced outside of the factory) type that implements the abstract class or interface.
  • Require a token that only the factory can provide in the constructor. This is how the DataTable class works. While the constructor could still be called, the user would have to pass in null for the value and it would at least be obvious that they shouldn't be doing this.
Adam Robinson
  • 182,639
  • 35
  • 285
  • 343
  • 1
    How would a RequireToken be type safe? Or enforced by the compiler? What is the type of the RequiredToken? – The Internet Oct 07 '13 at 16:15
  • @dave: It can't be *required*, in the sense that the user could still pass `null`. But it would fail at runtime and should be pretty obvious to the person writing the code that they shouldn't be calling it. – Adam Robinson Oct 07 '13 at 16:16
  • @dave: Also note that if the classes you're using are all in the same assembly, then the first two approaches would be preferable. The last approach is only necessary if you need to allow for subclasses defined outside of the defining assembly. – Adam Robinson Oct 07 '13 at 16:17
  • It seems that nested subclasses are the only way to enforce this by the compiler. – The Internet Oct 07 '13 at 16:19
  • If you mean both within and outside of the same assembly, then yes. – Adam Robinson Oct 07 '13 at 16:20
  • Yes, exactly. But then it becomes verbose – The Internet Oct 07 '13 at 16:24
  • 1
    You can always define the factory class as a partial class and still implement the nested sub classes in separate files. – Preston Guillot Oct 07 '13 at 16:32
  • Genius. partial classes, you win. – The Internet Oct 07 '13 at 16:32
  • I could bring my Factory class to inside the package that contains the created objects and then I made the constructor of those objects default (internal). Now, the only way to instantiate a new object is through the factory. Only calling the Factory class which does contain a public constructor can create the restricted objects. – brunoazev Mar 12 '20 at 21:42
3

The whole point of Factory Pattern is that only the Factory knows how to choose and make an object and it only exposes the instantiated object's functionality through an interface not a concrete class. Making the object's constructor private fails because Factory itself cannot instantiate it.

Solution:

1- Define an interface class which all types of the Extrude20XX classes implement it such as IExtrudeStuff.

2- Wrap the Extrude20XX classes inside the class of Factory as private nested classes.

3- Implement the interface IExtrude in all the ExtrudeXX classes.

4- Write a (static) Create (t) method like:

public static class ExtrudeFactory {
 public static IExtrudeStuff Create(ExtrudeType t) {
 {
   switch (t) {
       case ExtrudeType.Extrude2013: return new Extrude2013 ();
       case ExtrudeType.Extrude2014: return new Extrude2014 ();
       default: return null; 
   } 
 }
}
Alireza
  • 10,237
  • 6
  • 43
  • 59
  • This seems to be the solution, nested subclasses. What would be nice is to have seperate files for classes like Extrude2013 (that can't be instantiated), then have properties inside the factory for those classes that *can* be instantiated – The Internet Oct 07 '13 at 16:31
  • @dave Although it is a good practice, distributing the classes across separate files doesn't change anything unless they are in same assembly. The keynote is that they should be private inside the `Factory` class. This way `Factory` class can be partial and expands through out concrete classes. – Alireza Oct 07 '13 at 16:39
  • Yes completely it will not change the operational semantics of the code, but the difference is readability – The Internet Oct 07 '13 at 16:46
  • @dave and maintainability :) – Alireza Oct 07 '13 at 16:57