I'm looking for a clean design to emulate Visitor functionality without the many drawbacks it has.
In Java, the traditional implementations (as the described in GoF) resort to double dispatch to get rid of if-elses. To solve this, I've seen some implementations that use reflection to avoid modifications on the "Visitable" classes, but these rely on hardcoded strings when looking for method names. Although quite useful, I still think that they are not clean design.
Is it possible to emulate the same idea using data structures and/or good OO-design? It doesn't have to be a pattern, just I'm looking for examples where a similar problem is solved (e.g.: using a Map<Class<T>,SomeFunctionObject>).
UPDATE Something like this:
public abstract class BaseVisitor<T> {
private final TypesafeHeterogeneusMap map;
protected BaseVisitor(){
map = inflateFunctions();
}
public <E extends T> void process(E element){
if(element == null){
throw new NullPointerException();
}
boolean processed = false;
#SuppressWarnings("unchecked")
Class<? super T> sc = (Class<? super T>) element.getClass();
while(true){
if(sc != null){
FunctionObject<? super T> fo2 = map.get(sc);
if(fo2 != null){
fo2.process(element);
processed = true;
break;
}
sc = sc.getSuperclass();
} else {
break;
}
}
if(!processed) System.out.println("Unknown type: " + element.getClass().getName());
}
abstract TypesafeHeterogeneusMap inflateFunctions();
}
Actually is a mix of Template pattern and Command pattern, I think. Feel free to post your suggestions on how to enhance it.
You could just make all your Visitor implementations extend a base class, which provides a default implementation for every type of Visitable:
public interface AnimalVisitor {
void visitHorse(Horse horse);
void visitDog(Dog dog);
}
public class BaseAnimalVisitor implements AnimalVisitor {
public void visitHorse(Horse horse) {
// do nothing by default
}
public void visitDog(Dog dog) {
// do nothing by default
}
}
Then, when a new class Cat is introduced, you add the visitCat(Cat cat) method to the interface and the base class, and all the visitors are left unchanged and still compile. If they don't want to ignore cats, then you override the visitCat method.
Although it's not the answer you're looking for: Consider using a higher-level, less verbose language than Java. You will find that things like the Visitor pattern start to seem irrelevant. Of course, if you want to define logic for traversing a data structure in one place, and define what to do with the elements of the data structure (based on their types) somewhere else, and make it possible to mix-and-match traversal/processing strategies, you can do that. But you can do it using just a small amount of straightforward code, nothing that you would think of calling a "pattern".
I came from a C/Java programming background and started learning various dynamic languages a few years ago. It was mind-blowing to realize how much you can do in a few lines of code.
For example, if I was to emulate the Visitor pattern in Ruby:
module Enumerable
def accept_visitor(visitor)
each do |elem|
method = "visit#{elem.class}".to_sym
elem.send(method,elem) if elem.respond_to? method
end
end
end
To explain: in Ruby, an Enumerable represents anything which can be iterated over. In those 8 lines of code, I have made every kind of object which can be iterated over accept Visitors. Whether I plan to have 5, 10, or 100 different classes accept Visitors, those 8 lines are all that are needed.
Here's a sample Visitor:
class CatCounter
attr_reader :count
def initialize; #count = 0; end
def visitCat; #count += 1; end
end
Note that the Visitor doesn't have to define methods for all the different types of Visitables. Each Visitor just has to define methods for the types of Visitables it is interested in; it can ignore the rest. (Which means you don't have to modify a bunch of existing code if you add a new type of Visitable.) And any Visitor can interoperate with any object which accepts Visitors.
Just in those few lines of code, all the problems you mentioned with the Visitor pattern have been overcome.
Don't get me wrong; Java is a great language for some things. But you need to choose the right tool for the job. The fact that you are fighting so much to overcome the limitations of your tool might indicate that in this case, a different tool is called for.
#MisterSmith, since you have to use Java, and presumably you do have good reasons for using Visitor, I am going to propose another possible solution.
Let's separate our minds from the way Visitor is usually implemented and go back to the reason why people use Visitors in the first place. Although I mentioned it already in my other answer, the point of Visitor is to make it possible to mix-and-match traversal and processing logic.
"Traversal logic" could mean logic for traversing different types of data structures, or traversing the same data structure in a different order. Or it could even include traversal strategies which apply certain filters to the elements returned, etc.
Implicit in Visitor is the idea that the processing we apply to each element is going to depend on its class. If what we do to each element doesn't depend on its class, there is no reason to use Visitor. Unless we want to do a "switch" on element class, we need to use virtual method calls to do this (which is why the usual Java implementation uses double dispatch).
I propose that we can split the Visitor pattern into 3 rather than 2 parts:
An Iterator object which implements a certain traversal
An object which implements the strategy of "deciding what to do with an element based on its class" (the part which normally requires double dispatch). Using reflection, we can make a general-purpose class which does this. A simple implementation would use a Map, or you could make something which generates bytecode dynamically (I forget the platform method in Java which lets you load raw bytecodes as a new Class, but there is one). OR! OR, you could use a dynamic, JVM-hosted language like JRuby or Clojure to write #2, compile to bytecode, and use the resulting .class file. (This file would probably use the invokedynamic bytecode, which as far as I know, is not accessible from Java -- the Java compiler never emits it. If this has changed, please edit this post.)
The Visitors themselves. In this implementation, Visitors won't have to subclass from a common superclass, nor will they have to implement methods for elements they're not interested in.
Keeping the traversal in a general-purpose Iterator allows you to do other things with it (not just accepting Visitors).
There are a couple ways the 3 pieces could be tied together; I'm thinking #2 will wrap #3 (taking it as a constructor argument). #2 will provide a public method which takes an Iterator as an argument, and applies the Visitor to it.
The interesting part is #2. I may edit this post later to add a sample implementation; right now I have some other things to do. If someone else comes up with an implementation, please add it here.
I am trying to understand where good contracts end and paranoia starts.
Really, I just have no idea what good developer should care about and what shall he leave out :)
Let's say I have a class that holds value(s), like java.lang.Integer. Its instances are aggregated by other objects (MappedObjects), (one-to-many or many-to-many), and often used inside MappedObjects' methods. For performance reasons, I also track these relationships in TreeMap (guava MultiMap, doesn't matter) in addition, to be able to get fast iterations over MappedObjects bound to some range of Integer keys.
So, to keep system in consistent state, I should modify MappedObject.bind(Integer integer) method to update my Map like:
class MappedObject {
public void bind (Integer integer) {
MegaMap.getInstance().remove(fInteger, this);
fInteger = integer;
MegaMap.getInstance().add(fInteger, this);
}
...
private Integer fInteger;
}
I could just make abstract MappedObject class with this final method, forcing other to inherit from it, but it is rude. If I will define MappedObject as interface with method bind() and provide skeletal implementation -- other developer might later just forget to include it in object and implement method by himself without Map updating.
Yes, you should force people to do the right thing with your code. A great example of letting people do the wrong thing is the servlet method init( ServletConfig config ) that expected you would store the servlet config yourself but, obviously, a lot of people forgot to store the config and when running their servlets just failed to work.
When defining APIs, you should always follow the open-closed principle, your class should be open for extension and closed for modification. If your class has to work like this, you should only open extension points where they make sense, all the other functionality should not be available for modification, as it could lead to implementation issues in the future.
Try to focus on functionality first and leave all unnecessary things behind. Btw you can't prohibit reflection so don't worry too much on misuse. On the other hand your API should be clear and straightforward so users will have clear idea, what they should and what they shouldn't do with it.
I'd say your classes should be designed for as simple use as possible.
If you allow a developer to override methods you definitely should document the contract as good as possible. In that case the developer opts to override some basic functionality and thus is responsible to provide an implementation that adheres to the contract.
In cases where you don't want the developer to override parts of the functionality - for security reasons, if there is no sensible alternative etc. - just make that part final. In your case, the bind method might look like this:
class MappedObject {
public final void bind (Integer integer) {
MegaMap.getInstance().remove(fInteger);
internalBind( integer );
MegaMap.getInstance().add(fInteger);
}
protected void internalBind( Integer integer ) {
fInteger = integer;
}
...
private Integer fInteger;
}
Here you'd allow the developer to override the internalBind() method but ensure that bind() will do the mapping.
To summarize: Make using and extending classes as easy as (sensibly) possible and don't have the developer to copy lots of boiler plate code (like the map updates in your case) in case he just wants to override some basic functionality (like the actual binding).
At least you should do really everything that prevents bugs but cost no effort.
For example: use primitive types (int) instead of wrappers (Integer) if the variable is not allowed to be null.
So in your bind method. If you not have intended to bind null, then use int instead of Integer as parameter type.
If you think your API users are stupid, you should prohibit wrong usage. Otherwise you should not stand in their way to do things they need to do.
Domumentation and good naming of classes and methods should indicate how to use your API.
Lately I've been struggling with some recurrent design problem which I don't know how to solve elegantly.
Say I am making a game with a couple of players and for each player some connected pieces. Together these pieces form a semi-complex collection or structure. Now I could implement this structure in 2 ways: Either store the structure implicitly through pointers in the pieces themselves i.e:
class BigPiece extends Piece {
Piece opposingPiece, nextPiece, previousPiece, index;
}
Or I could implement this structure in a collection class and keep the information centralized:
class SomeCollection<Collection<Piece>> {
SomeOtherCollection<Collection<Piece>> collection
= new SomeOtherCollection<Collection<Piece>>();
public SomeCollection() {
collection.add(new PieceCollection<Piece>();
collection.add(new PieceCollection<Piece>();
collection.add(new PieceCollection<Piece>();
}
public Piece getPiece(int playerIndex, int pieceIndex) {
collection.get(playerIndex).get(pieceIndex);
}
public Piece getOpposingPiece(int playerIndex, int pieceIndex) {
int nextPlayerIndex = collection.listIterator(playerIndex).nextIndex();
return this.collection.get(nextPlayerIndex).get(pieceIndex);
}
}
Now I usually favor the second one, but that's just based on my guts and I don't have that much experience in class design, especially not with big applications. I can see pros and cons on both sides.
The problem I usually have with the first solution is that you still have to create the associations in some builder or factory which actually links the objects together. This doesn't seem very robust to me. Who can reassure me all the pointers are actually correct throughout the application's lifetime?
The second solution centralizes the data more. This really dumbs down the higher classes though (such as individual Pieces). The problem I usually have with this is that whenever I want to traverse this collection, I have to do it on some lower level. You can't ask a piece 'Hey, what's your opposing piece?'. No, you'd have to get a game object to get a pointer to your collection which you then ask what the opposing piece is. This makes more 'managery' classes which collect data from all around your application (method chaining =( ) to finally implement your algorithm. This seems to violate the Law of Demeter.
Sure I could add a pointer to the corresponding collection from each individual piece as well, but I don't know if that's such a good idea since this only seems to be duplicate information.
My personal recommendation is moreso the second option as opposed to the first. As you pointed out, a piece shouldn't (at least in this context) know what its opposing/next/previous piece is.
A manager class would make more logical sense to better facilitate communication between the classes instead of pieces having references to other pieces. I admit I don't fully know about the Law of Demeter but Wikipedia leads me to believe it is all about encapsulation which the manager classes would actually help as well!
I don't think Pieces (again, in this context) should be able to, say, move another piece. However a manager class would logically want to.
That is my suggestion, I hope it helps!
class MyThing {
protected HashMap<String,Object> fields;
protected MyThing(HashMap<String,Object> newFields){
fields.putAll(newFields);
}
protected Object get(String key){
return fields.get(key);
}
}
Now a little background. I am using this class as a super class to a bunch of different classes which represent objects from an XML file. This is basically an implementation of an API wrapper and I am using this as an adapter between the parsed XML from an API and a database. Casting is delegated to the caller of the get method. If the subclasses need to do something when they are created or when they return a variable, they just call super and then manipulate what gets returned afterwards. eg.:
class Event extends MyThing {
public Event(HashMap<String,Object> newFields){
super(newFields);
// Removes anything after an # symbol in returned data
Pattern p = Pattern.compile("\\#.*$");
Matcher m = p.matcher((String)fields.get("id"));
boolean result = m.find();
if (result)
fields.put("id", m.replaceFirst(""));
}
}
public Object get(String key){
Object obj = super(key);
if (key.equals("name")){
return "Mr./Mrs. " + ((String)obj);
}
}
}
The reason I feel like I should do this is so I don't have to write getId, getName, getWhatever methods for every single subclass just because they have different attributes. It would save time and it is pretty self explanatory.
Now this is obviously "unJavalike" and more like a ducktyped language way of doing things, but is there a logical reason why I should absolutely not be doing this?
If you're going to this level of complexity and mucking up your object model just because you don't want to have getters and setters, do it in Groovy instead.
Groovy is a duck typed dynamic language on the JVM that accepts 98% of valid Java code, so you already know most of the language (you don't lose functionality)...there are "more idiomatic" ways of doing things, but you can pick those up with time. It also already has a built in XmlSlurper, which probably does most of what you're trying to do anyway.
As for the "reasons why you shouldn't", you're introducing all types of maintainability concerns.
New classes will always have to derive from the base class.
They will have to implement a constructor that always calls a base constructor
They will have to override get() [which you're basically using to encapsulate your getters and setters anyway, why not just add that method and delegate to those other methods] and write specific logic which is likely to degrade with time.
Why shouldn't you? It'll work, right? Sure. But it's poor engineering in that you're either creating a maintenance nightmare, or reinventing the wheel and likely to do it wrong.
Obviously, it's not type safe.
Future maintainers won't know what the types are supposed to be and will get generally confused as to why you're not using POJOs.
Instead of constant time, space complexity and performance you have the characteristics of a HashMap.
It become very difficult to write non-trivial getters/setters in future.
Most data binding systems are designed to work with POJOs/Beans (JAXB, JPA, Jackson, etc).
I'm sure there are more, but this will do. Try using some proper OXM libraries and you'll be much better off.
I keep hearing the statement on most programming related sites:
Program to an interface and not to an Implementation
However I don't understand the implications?
Examples would help.
EDIT: I have received a lot of good answers even so could you'll supplement it with some snippets of code for a better understanding of the subject. Thanks!
You are probably looking for something like this:
public static void main(String... args) {
// do this - declare the variable to be of type Set, which is an interface
Set buddies = new HashSet();
// don't do this - you declare the variable to have a fixed type
HashSet buddies2 = new HashSet();
}
Why is it considered good to do it the first way? Let's say later on you decide you need to use a different data structure, say a LinkedHashSet, in order to take advantage of the LinkedHashSet's functionality. The code has to be changed like so:
public static void main(String... args) {
// do this - declare the variable to be of type Set, which is an interface
Set buddies = new LinkedHashSet(); // <- change the constructor call
// don't do this - you declare the variable to have a fixed type
// this you have to change both the variable type and the constructor call
// HashSet buddies2 = new HashSet(); // old version
LinkedHashSet buddies2 = new LinkedHashSet();
}
This doesn't seem so bad, right? But what if you wrote getters the same way?
public HashSet getBuddies() {
return buddies;
}
This would have to be changed, too!
public LinkedHashSet getBuddies() {
return buddies;
}
Hopefully you see, even with a small program like this you have far-reaching implications on what you declare the type of the variable to be. With objects going back and forth so much it definitely helps make the program easier to code and maintain if you just rely on a variable being declared as an interface, not as a specific implementation of that interface (in this case, declare it to be a Set, not a LinkedHashSet or whatever). It can be just this:
public Set getBuddies() {
return buddies;
}
There's another benefit too, in that (well at least for me) the difference helps me design a program better. But hopefully my examples give you some idea... hope it helps.
One day, a junior programmer was instructed by his boss to write an application to analyze business data and condense it all in pretty reports with metrics, graphs and all that stuff. The boss gave him an XML file with the remark "here's some example business data".
The programmer started coding. A few weeks later he felt that the metrics and graphs and stuff were pretty enough to satisfy the boss, and he presented his work. "That's great" said the boss, "but can it also show business data from this SQL database we have?".
The programmer went back to coding. There was code for reading business data from XML sprinkled throughout his application. He rewrote all those snippets, wrapping them with an "if" condition:
if (dataType == "XML")
{
... read a piece of XML data ...
}
else
{
.. query something from the SQL database ...
}
When presented with the new iteration of the software, the boss replied: "That's great, but can it also report on business data from this web service?" Remembering all those tedious if statements he would have to rewrite AGAIN, the programmer became enraged. "First xml, then SQL, now web services! What is the REAL source of business data?"
The boss replied: "Anything that can provide it"
At that moment, the programmer was enlightened.
An interface defines the methods an object is commited to respond.
When you code to the interface, you can change the underlying object and your code will still work ( because your code is agnostic of WHO do perform the job or HOW the job is performed ) You gain flexibility this way.
When you code to a particular implementation, if you need to change the underlying object your code will most likely break, because the new object may not respond to the same methods.
So to put a clear example:
If you need to hold a number of objects you might have decided to use a Vector.
If you need to access the first object of the Vector you could write:
Vector items = new Vector();
// fill it
Object first = items.firstElement();
So far so good.
Later you decided that because for "some" reason you need to change the implementation ( let's say the Vector creates a bottleneck due to excessive synchronization)
You realize you need to use an ArrayList instad.
Well, you code will break ...
ArrayList items = new ArrayList();
// fill it
Object first = items.firstElement(); // compile time error.
You can't. This line and all those line who use the firstElement() method would break.
If you need specific behavior and you definitely need this method, it might be ok ( although you won't be able to change the implementation ) But if what you need is to simply retrieve the first element ( that is , there is nothing special with the Vector other that it has the firstElement() method ) then using the interface rather than the implementation would give you the flexibility to change.
List items = new Vector();
// fill it
Object first = items.get( 0 ); //
In this form you are not coding to the get method of Vector, but to the get method of List.
It does not matter how do the underlying object performs the method, as long as it respond to the contract of "get the 0th element of the collection"
This way you may later change it to any other implementation:
List items = new ArrayList(); // Or LinkedList or any other who implements List
// fill it
Object first = items.get( 0 ); // Doesn't break
This sample might look naive, but is the base on which OO technology is based ( even on those language which are not statically typed like Python, Ruby, Smalltalk, Objective-C etc )
A more complex example is the way JDBC works. You can change the driver, but most of your call will work the same way. For instance you could use the standard driver for oracle databases or you could use one more sophisticated like the ones Weblogic or Webpshere provide . Of course it isn't magical you still have to test your product before, but at least you don't have stuff like:
statement.executeOracle9iSomething();
vs
statement.executeOracle11gSomething();
Something similar happens with Java Swing.
Additional reading:
Design Principles from Design Patterns
Effective Java Item: Refer to objects by their interfaces
( Buying this book the one of the best things you could do in life - and read if of course - )
My initial read of that statement is very different than any answer I've read yet. I agree with all the people that say using interface types for your method params, etc are very important, but that's not what this statement means to me.
My take is that it's telling you to write code that only depends on what the interface (in this case, I'm using "interface" to mean exposed methods of either a class or interface type) you're using says it does in the documentation. This is the opposite of writing code that depends on the implementation details of the functions you're calling. You should treat all function calls as black boxes (you can make exceptions to this if both functions are methods of the same class, but ideally it is maintained at all times).
Example: suppose there is a Screen class that has Draw(image) and Clear() methods on it. The documentation says something like "the draw method draws the specified image on the screen" and "the clear method clears the screen". If you wanted to display images sequentially, the correct way to do so would be to repeatedly call Clear() followed by Draw(). That would be coding to the interface. If you're coding to the implementation, you might do something like only calling the Draw() method because you know from looking at the implementation of Draw() that it internally calls Clear() before doing any drawing. This is bad because you're now dependent on implementation details that you can't know from looking at the exposed interface.
I look forward to seeing if anyone else shares this interpretation of the phrase in the OP's question, or if I'm entirely off base...
It's a way to separate responsibilities / dependancies between modules.
By defining a particular Interface (an API), you ensure that the modules on either side of the interface won't "bother" one another.
For example, say module 1 will take care of displaying bank account info for a particular user, and module2 will fetch bank account info from "whatever" back-end is used.
By defining a few types and functions, along with the associated parameters, for example a structure defining a bank transaction, and a few methods (functions) like GetLastTransactions(AccountNumber, NbTransactionsWanted, ArrayToReturnTheseRec) and GetBalance(AccountNumer), the Module1 will be able to get the needed info, and not worry about how this info is stored or calculated or whatever. Conversely, the Module2 will just respond to the methods call by providing the info as per the defined interface, but won't worry about where this info is to be displayed, printed or whatever...
When a module is changed, the implementation of the interface may vary, but as long as the interface remains the same, the modules using the API may at worst need to be recompiled/rebuilt, but they do not need to have their logic modified in anyway.
That's the idea of an API.
At its core, this statement is really about dependencies. If I code my class Foo to an implementation (Bar instead of IBar) then Foo is now dependent on Bar. But if I code my class Foo to an interface (IBar instead of Bar) then the implementation can vary and Foo is no longer dependent on a specific implementation. This approach gives a flexible, loosely-coupled code base that is more easily reused, refactored and unit tested.
Take a red 2x4 Lego block and attach it to a blue 2x4 Lego block so one sits atop the other. Now remove the blue block and replace it with a yellow 2x4 Lego block. Notice that the red block did not have to change even though the "implementation" of the attached block varied.
Now go get some other kind of block that does not share the Lego "interface". Try to attach it to the red 2x4 Lego. To make this happen, you will need to change either the Lego or the other block, perhaps by cutting away some plastic or adding new plastic or glue. Notice that by varying the "implementation" you are forced to change it or the client.
Being able to let implementations vary without changing the client or the server - that is what it means to program to interfaces.
An interface is like a contract between you and the person who made the interface that your code will carry out what they request. Furthermore, you want to code things in such a way that your solution can solve the problem many times over. Think code re-use. When you are coding to an implementation, you are thinking purely of the instance of a problem that you are trying to solve. So when under this influence, your solutions will be less generic and more focused. That will make writing a general solution that abides by an interface much more challenging.
Look, I didn't realize this was for Java, and my code is based on C#, but I believe it provides the point.
Every car have doors.
But not every door act the same, like in UK the taxi doors are backwards. One universal fact is that they "Open" and "Close".
interface IDoor
{
void Open();
void Close();
}
class BackwardDoor : IDoor
{
public void Open()
{
// code to make the door open the "wrong way".
}
public void Close()
{
// code to make the door close properly.
}
}
class RegularDoor : IDoor
{
public void Open()
{
// code to make the door open the "proper way"
}
public void Close()
{
// code to make the door close properly.
}
}
class RedUkTaxiDoor : BackwardDoor
{
public Color Color
{
get
{
return Color.Red;
}
}
}
If you are a car door repairer, you dont care how the door looks, or if it opens one way or the other way. Your only requirement is that the door acts like a door, such as IDoor.
class DoorRepairer
{
public void Repair(IDoor door)
{
door.Open();
// Do stuff inside the car.
door.Close();
}
}
The Repairer can handle RedUkTaxiDoor, RegularDoor and BackwardDoor. And any other type of doors, such as truck doors, limousine doors.
DoorRepairer repairer = new DoorRepairer();
repairer.Repair( new RegularDoor() );
repairer.Repair( new BackwardDoor() );
repairer.Repair( new RedUkTaxiDoor() );
Apply this for lists, you have LinkedList, Stack, Queue, the normal List, and if you want your own, MyList. They all implement the IList interface, which requires them to implement Add and Remove. So if your class add or remove items in any given list...
class ListAdder
{
public void PopulateWithSomething(IList list)
{
list.Add("one");
list.Add("two");
}
}
Stack stack = new Stack();
Queue queue = new Queue();
ListAdder la = new ListAdder()
la.PopulateWithSomething(stack);
la.PopulateWithSomething(queue);
Allen Holub wrote a great article for JavaWorld in 2003 on this topic called Why extends is evil. His take on the "program to the interface" statement, as you can gather from his title, is that you should happily implement interfaces, but very rarely use the extends keyword to subclass. He points to, among other things, what is known as the fragile base-class problem. From Wikipedia:
a fundamental architectural problem of object-oriented programming systems where base classes (superclasses) are considered "fragile" because seemingly safe modifications to a base class, when inherited by the derived classes, may cause the derived classes to malfunction. The programmer cannot determine whether a base class change is safe simply by examining in isolation the methods of the base class.
In addition to the other answers, I add more:
You program to an interface because it's easier to handle. The interface encapsulates the behavior of the underlying class. This way, the class is a blackbox. Your whole real life is programming to an interface. When you use a tv, a car, a stereo, you are acting on its interface, not on its implementation details, and you assume that if implementation changes (e.g. diesel engine or gas) the interface remains the same. Programming to an interface allows you to preserve your behavior when non-disruptive details are changed, optimized, or fixed. This simplifies also the task of documenting, learning, and using.
Also, programming to an interface allows you to delineate what is the behavior of your code before even writing it. You expect a class to do something. You can test this something even before you write the actual code that does it. When your interface is clean and done, and you like interacting with it, you can write the actual code that does things.
"Program to an interface" can be more flexible.
For example, we are writing a class Printer which provides print service. currently there are 2 class (Cat and Dog) need to be printed. So we write code like below
class Printer
{
public void PrintCat(Cat cat)
{
...
}
public void PrintDog(Dog dog)
{
...
}
...
}
How about if there is a new class Bird also needs this print service? We have to change Printer class to add a new method PrintBird(). In real case, when we develop Printer class, we may have no idea about who will use it. So how to write Printer? Program to an interface can help, see below code
class Printer
{
public void Print(Printable p)
{
Bitmap bitmap = p.GetBitmap();
// print bitmap ...
}
}
With this new Printer, everything can be printed as long as it implements Interface Printable. Here method GetBitmap() is just a example. The key thing is to expose an Interface not a implementation.
Hope it's helpful.
Essentially, interfaces are the slightly more concrete representation of general concepts of interoperation - they provide the specification for what all the various options you might care to "plug in" for a particular function should do similarly so that code which uses them won't be dependent on one particular option.
For instance, many DB libraries act as interfaces in that they can operate with many different actual DBs (MSSQL, MySQL, PostgreSQL, SQLite, etc.) without the code that uses the DB library having to change at all.
Overall, it allows you to create code that's more flexible - giving your clients more options on how they use it, and also potentially allowing you to more easily reuse code in multiple places instead of having to write new specialized code.
By programming to an interface, you are more likely to apply the low coupling / high cohesion principle.
By programming to an interface, you can easily switch the implementation of that interface (the specific class).
It means that your variables, properties, parameters and return types should have an interface type instead of a concrete implementation.
Which means you use IEnumerable<T> Foo(IList mylist) instead of ArrayList Foo(ArrayList myList) for example.
Use the implementation only when constructing the object:
IList list = new ArrayList();
If you have done this you can later change the object type maybe you want to use LinkedList instead of ArrayList later on, this is no problem since everywhere else you refer to it as just "IList"
It's basically where you make a method/interface like this: create( 'apple' ) where the method create(param) comes from an abstract class/interface fruit that is later implemented by concrete classes. This is different than subclassing. You are creating a contract that classes must fulfill. This also reduces coupling and making things more flexible where each concrete class implements it differently.
The client code remains unaware of the specific types of objects used and remains unaware of the classes that implement these objects. Client code only knows about the interface create(param) and it uses it to make fruit objects. It's like saying, "I don't care how you get it or make it I, just want you to give it to me."
An analogy to this is a set of on and off buttons. That is an interface on() and off(). You can use these buttons on several devices, a TV, radio, light. They all handle them differently but we don't care about that, all we care about is to turn it on or turn it off.
Coding to an interface is a philosophy, rather than specific language constructs or design patterns - it instructs you what is the correct order of steps to follow in order to create better software systems (e.g. more resilient, more testable, more scalable, more extendible, and other nice traits).
What it actually means is:
===
Before jumping to implementations and coding (the HOW) - think of the WHAT:
What black boxes should make up your system,
What is each box' responsibility,
What are the ways each "client" (that is, one of those other boxes, 3rd party "boxes", or even humans) should communicate with it (the API of each box).
After you figure the above, go ahead and implement those boxes (the HOW).
Thinking first of what a box' is and what its API, leads the developer to distil the box' responsibility, and to mark for himself and future developers the difference between what is its exposed details ("API") and it's hidden details ("implementation details"), which is a very important differentiation to have.
One immediate and easily noticeable gain is the team can then change and improve implementations without affecting the general architecture. It also makes the system MUCH more testable (it goes well with the TDD approach).
===
Beyond the traits I've mentioned above, you also save A LOT OF TIME going this direction.
Micro Services and DDD, when done right, are great examples of "Coding to an interface", however the concept wins in every pattern from monoliths to "serverless", from BE to FE, from OOP to functional, etc....
I strongly recommend this approach for Software Engineering (and I basically believe it makes total sense in other fields as well).