This answer is touching on things mentioned in other answers, but I wanted to explain it in a way I find easier to understand.
There is a huge difference between data and logic. Data is much more easily changeable than logic. Almost by definition, data is something that can change without requiring redevelopment, whereas changes to logic always need some kind of development effort.
However, when you have data alongside your logic, you have to integrate your logic to use that data. Developing logic that handles data takes a non-zero amount of effort. If the data in question doesn't change, then there's no real point to abstracting it as data. In such a case, we hardcode the information in the logic. It's the least-effort approach.
And don't get me wrong, that's not inherently bad to do. Some thing simply don't need to be abstracted. If we abstracted literally everything that could be abstracted, we'd have countless more layers of abstractions in any codebase, without really adding value to that codebase.
There should only be as many abstractions as you need; not as many as you can implement.
Take the example of a bible salesman. They only sell bibles, no other books, and therefore the "bible" concept can be hardcoded in this bible salesman's codebase:
public void SellBibleTo(Customer c) { ... }
This is perfectly fine as long as they only sell bibles.
But just like in your situation, that rigidity has ceased to exist when this salesman begins selling other books as well. At this point, the code needs to be re-evaluated to account for a much more abstract approach: the ability to sell any kind of book.
It is at this point that the hardcoded needs to disappear, and we need to introduce actual data (including handling) that allows for different values to be used, in this case books.
The codebase will end up with another layer of abstraction:
public class Book { ... }
public void SellTo(Customer c, Book b) { ... }
This is more than just a rename. That's the most important thing to take away from this.
Now, your code can no longer assume that it innately knows which book is being sold, and thus the choice of book becomes an input parameter to your book-handling (previously bible-handling) logic.
Moving back to your example, the issue with your approach is that you are merely attempting to rename your logic rather than expand the logic itself.
If your client has decided to also handle glasses instead of just cups, it becomes clear to see that products can change. It's highly likely that your client is going to handle a third, fourth, fifth, ... kind of product down the line.
When things need to be able to change, they are data that can no longer be hardcoded. Therefore, just like how we needed to expand our example logic to handle any kind of book, now we need to rewrite the logic to handle any kind of product. I'm calling it a "product" for lack of a better name, I don't know your company's market focus.
I was going to refactor your code example but it's not a particularly meaningful one to rewrite since you're only using a hardcoded parameterless constructor in a factory whose name doesn't describe its purpose.
So I'm going to create my own example here. If your old code was this:
public void ShipCupToCustomer(Customer customer)
{
var cup = new Cup();
washer.Rinse(cup);
var box = packer.Package(cup);
labeler.Label(box, customer.Address);
shipper.Ship(box);
}
Then your new code would be something along the lines of:
public abstract class Product { }
public class Cup : Product { }
public class Glass : Product { }
public void ShipProductToCustomer(Product product, Customer customer)
{
washer.Rinse(product);
var box = packer.Package(product);
labeler.Label(box, customer.Address);
shipper.Ship(box);
}
It's not a huge change, but it is an extra level of abstraction.
Note that this will also cause you to have to change the washer
and packer
objects' interfaces to account for all Product
types, but this example is meant to be short and simple.
Being able to spot this trend early is going to enable you to be prepared when it does eventually happen. It's okay that you didn't spot it during the first version, but now that you have direct experience with this kind of thing happening, you should respond to it accordingly.
Blue red;
are mala praxis. – user375277 Sep 23 '20 at 1:19