Search code examples
classmember-functionsnon-member-functions

Excluding member functions and inheritance, what are some of the most common programming patterns for adding functionality to a class?


There're likely no more than 2-4 widely used approaches to this problem.

I have a situation in which there's a common class I use all over the place, and (on occasion) I'd like to give it special abilities. For arguments sake, let's say that type checking is not a requirement.

What are some means of giving functionality to a class without it being simply inheritance or member functions?

  1. One way I've seen is the "decorator" pattern in which a sort of mutator wraps around the class, modifies it a bit, and spits out a version of it with more functions.

  2. Another one I've read about but never used is for gaming. It has something to do with entities and power-ups/augments. I'm not sure about the specifics, but I think they have a list of them.

  3. ???

I don't need specific code of a specific language so much as a general gist and some keywords. I can implement from there.


Solution

  • So as far as I understand, you're looking to extend an interface to allow client-specific implementations that may require additional functionality, and you want to do so in a way that doesn't clutter up the base class.

    As you mentioned, for simple systems, the standard way is to use the Adaptor pattern: subclass the "special abilities", then call that particular subclass when you need it. This is definitely the best choice if the extent of the special abilities you'll need to add is known and reasonably small, i.e. you generally only use the base class, but for three-to-five places where additional functionality is needed.

    But I can see why you'd want some other possible options, because rarely do we know upfront the full extent of the additional functionality that will be required of the subclasses (i.e. when implementing a Connection API or a Component Class, each of which could be extended almost without bound). Depending on how complex the client-specific implementations are, how much additional functionality is needed and how much it varies between the implementations, this could be solved in a variety of ways:

    • Decorator Pattern as you mentioned (useful in the case where the special entities are only ever expanding the pre-existing methods of the base class, without adding brand new ones)

      class MyClass{};
      DecoratedClass = decorate(MyClass);
      
    • A combined AbstractFactory/Adaptor builder for the subclasses (useful for cases where there are groupings of functionality in the subclasses that may differ in their implementations)

        interface Button { 
            void paint(); 
        }
    
        interface GUIFactory { 
            Button createButton(); 
        }
    
        class WinFactory implements GUIFactory { 
            public Button createButton() { 
                return new WinButton(); 
            } 
        }
    
        class OSXFactory implements GUIFactory {
            public Button createButton() { 
                return new OSXButton(); 
            } 
        } 
    
        class WinButton implements Button { 
            public void paint() {
                System.out.println("I'm a WinButton"); 
            } 
        } 
    
        class OSXButton implements Button { 
            public void paint() { 
                System.out.println("I'm an OSXButton"); 
            } 
        } 
    
        class Application { 
            public Application(GUIFactory factory) { 
                Button button = factory.createButton();
                button.paint(); 
            }
         }
    
        public class ApplicationRunner { 
            public static void main(String[] args) { 
                new Application(createOsSpecificFactory()); 
            }
    
        public static GUIFactory createOsSpecificFactory() {
            int sys = readFromConfigFile("OS_TYPE");
            if (sys == 0) return new WinFactory(); 
            else return new OSXFactory(); 
            }
        }
    
    • The Strategy pattern could also work, depending on the use case. But that would be a heavier lift with the preexisting base class that you don't want to change, and depending on if it is a strategy that is changing between those subclasses. The Visitor Pattern could also fit, but would have the same problem and involve a major change to the architecture around the base class.

      class MyClass{
        public sort() { Globals.getSortStrategy()() }
      };
      
    • Finally, if the "special abilities" needed are enough (or could eventually be enough) to justify a whole new interface, this may be a good time for the use of the Extension Objects Pattern. Though it does make your clients or subclasses far more complex, as they have to manage a lot more: checking that the specific extension object and it's required methods exist, etc.

      class MyClass{
        public addExtension(addMe) {
          addMe.initialize(this);
        }
        public getExtension(getMe);
      };
      (new MyClass()).getExtension("wooper").doWoop();
      

    With all that being said, keep it as simple as possible, sometimes you just have to write the specific subclasses or a few adaptors and you're done, especially with a preexisting class in use in many other places. You also have to ask how much you want to leave the class open for further extension. It might be worthwhile to keep the tech debt low with an abstract factory, so less changes need to be made when you add more functionality down the road. Or maybe what you really want is to lock the class down to prevent further extension, for the sake of understand-ability and simplicity. You have to examine your use case, future plans, and existing architecture to decide on the path forward. More than likely, there are lots of right answers and only a couple very wrong ones, so weigh the options, pick one that feels right, then implement and push code.