One of the essential concepts to understand is flexibility in OOP. A good design requires the ability to extend it. There are ways to do this. However, they require us to incorporate mechanisms for validation and growth. That means we need to find a balance between the core to our functionality and what can be altered as an enhancement.
Flexibility in OOP - Many OptionsThere are multiple ways to build flexibility into your object-oriented design. We can use parameters that determine functionality, check for class types, or even plugin commands. These are critical for an effective design as we want to keep our solution flexible enough to grow. Anything rigid is also going to be fragile to some extent. Therefore, we should build a solution that bends without breaking. Likewise, we want to minimize assumptions. We can never be sure where developers will want to take our code.
Strict LanguageOne thing to keep in mind with these hooks that you supply is including restrictions. For example, we can use enumerated types and exceptions to ensure users stick within reason for parameters. This approach allows us to leave room for growth while still requiring code to work within the given framework. We can have a print that takes a parameter for output type that only works when the type is within a collection to enumeration. Then the set of valid options can be extended as needed.
Ground RulesThe simplest way to think about this need for some flexibility, but not too much, is as ground rules. There are certain things your code will expect to be available. Thus, exceptions will be thrown when those requirements are not met.