Let's go on an imaginary journey in class design for a minute.
Suppose that you have the following classes representing vehicles. You go the poly...
For further actions, you may consider blocking this person and/or reporting abuse
***Sorry for clicking "Edit Post" like 20 times, but I kept thinking of more ways interfaces really help here.
Code to interfaces, not concrete implementations?
if you have functionality that you're wrapping, that's fine too.
define an abstract class that contains common functionality, and make sure it implements the interface, then just subclass it and set your custom implementations in the constructors of base classes
That is exactly how I would approach the problem. This approach is a combination of SOLID principles (interface segregation and dependency inversion mostly) and Bridge pattern (decouple an abstraction from its implementation so that the two can vary independently). Also,
"if you have functionality that you're wrapping, that's fine too.",
we can use Template pattern here.
Perhaps I'm glossing over things.. correct me if I am:
If I look at this:
This looks like you want to host a variety of L, E, I and V instances. But the thing that really varies is the kind of Sedan.
If you abstract the thing that's similar yet varies, you'll end up with a less elaborate type signature and the class could look roughly like this:
This answer is going to be a little bit fuzzy and vague. One suggestion is to start by writing a single implementation end-to-end without using inheritance, then another, and then apply the inheritance if and when it makes sense. Starting with inheritance often paints us into a corner.
In the case of a class like this:
Inheriting from
VehicleAssemblyLine
suggests polymorphism. But if the class is going to be cast as its base type then where will theSedanMakersUnionType
fit in?Also, we typically use a factory to create a class when we need it. That means that in any given scenario we need one instance of a specific class. If we're trying to create numerous instances of multiple objects of varying types, what needs them?
I realize it's just an example. But perhaps what I'm trying to say is that it's better if we start from something simple that achieves one instance of exactly what we need when we need it. This sort of problem happens when we work the other way, trying to create a "generic" solution for a requirement that hasn't been clearly defined.
I understand because I've gotten to the same point many, many times.
I hope the title of this post - The Generic Rabbit Hole of Madness - doesn't seem derogatory, because that's not the intent. But this seems like a perfect case of that. I wrote the post because I've done it so many times.
Yeah I saw that post come out a few days after mine and immediately added it to my Pocket list-- thanks! :)
I know this is in C#, but if this were general question for some abstract language then the problem might be solved with what Scala calls Abstract Types (I think it's better to call them Abstract Member Types).
If you just want to solve it in C#, read no further in my post, because this is of no help (sorry about that).
Otherwise see:
The idea is to move the types from generic parameters to abstract (member) types:
What this means is that sub-classes of VehicleAssemblyLine have to provide three type definitions of E, I and V that must sub-class described.
This at first does not seem to be that much different than providing them as generic arguments, but when you sub-class it makes more sense:
The idea is that Sedan provides a more concrete definition of type E, so all the inherited methods of Sedan from Vehicle return the more specific type. It essentially moves the parameterization from the declaration site into the body. This reduces all the generic arguments and reduces exponential growth of generic parameters.
For example, you can still refer to a VehicleFactory without all the generic arguments, and if required you can do safe casting to SedanFactory or others as required.
(If this were C++ you might be able to use type traits to simulate all of this).
I'm not sure what other languages have abstract member types? Possibly Swift's associated types are a similar thing?
Perhaps C# and other languages will adopt this feature in later releases. Until then simulating it in C# means using generics and lots of casting and typing. Ugh.
I've actually spent a lot of time thinking about problems like this and there are some rules of thumb I use when I get stuck on some architecture design.
Before typing any code (or thinking about architecture design) make sure you fully understand the problem you're trying to solve and how much that problem can vary. If you don't understand the problem then you can't model the solution right (for instance maybe you think penguins aren't birds because they can't fly). If you don't have a vision on how much a problem can vary you won't know how much flexibility you require. You may overcomplicate.
When not sure how to model a solution try to brute force it. Start typing some code and notice which pieces are appearing over and over and then simply collect those pieces into an abstraction. This way, a good model for your problem will be a bit more clear. You need to make sure abstractions contain only code common to all concrete implementations. This advice sounds silly but it's not always easy to tell what's common and what's specific and sometimes you don't notice you made mistake in that regard.
My advice is to take a step back from this issue. It's imaginary. For instance it's hard to tell do you really require a class for both assembly line and assembly line instructions? Do you need to pass instructions to the assembly line as a parameter (maybe one instruction applies to only one assembly line)? What is a lead engineer class? Is that just data or does it have some complicated methods? Too many foggy questions and ofcourse we will have a hard time thinking of a proper model for this solution.
One more advice and this one sort of comes from 1. and 2. I would suggest you design your models from concrete implementations to their abstractions. This way you won't find yourself in a situation where you require a switch case cast hell. Ofcourse to model like this, rule 1. already needs to be covered.
For the end just a short note. For now I never needed to use generics in my architecture designs. There was always a better alternative (even a small cast can be better). Still generics are awesome when it comes to data structures but how often do you type those?
I don't have much to add but I second this sentiment. It's an interesting conversation.
Really awesome discussion that I'd love to read some expert opinions on :)