37

According to Understanding "programming to an interface", as I understand, I think I should depend on abstract class only. However, in some case, for example, Student:

public class Student {
    private String name;
    private int age;
}

to modify it so that it becomes depend on abstract class only (which MyIString may be a new abstract class that wraps String):

public class Student {
    private MyIString name;
    private java.lang.Number age;
}

I think the modified one is more complex. And a more "real" example, say Address:

public class Address {
    private ZipCode zipcode;
}

which I need one type of ZipCode only, but if I modify it as:

public class Address {
    private IZipCode zipcode;
}

which IZipCode is an interface, then I think it may mislead other teammates that there would have other types of ZipCodes.

I think the cases above becomes more complex and less maintainable if I was allowed a class to use abstract class members only. So my question is, should I still follow "programming to an interface not implementation" if the "followed" one becomes more complex (in my view)?

Sisir
  • 828
  • 1
  • 7
  • 17
aacceeggiikk
  • 707
  • 1
  • 5
  • 6
  • 4
    How did you come to that understanding? – Theraot Dec 04 '19 at 10:35
  • 40
    Addresses are complex—have you ever seen some from dramatically foreign countries? How can you assume there won’t be multiple ZipCode implementations? – D. Ben Knoble Dec 04 '19 at 13:23
  • 23
    Somewhere you **must** use concrete types, otherwise nothing would run. It all depends on *where* and *how* you use them. For example: every object is just a bunch of bytes containing data and instructions... nothing stops you from using a plain `java.lang.String` in place of all the objects! Your functions will just interpret the String to obtain the information and the instructions to execute and you wont need any other type than `String`! That's clearly going too far in using concrete types. – Bakuriu Dec 04 '19 at 18:30
  • 20
    You should read more about addresses and how little you know: https://www.mjt.me.uk/posts/falsehoods-programmers-believe-about-addresses/ – BobDalgleish Dec 04 '19 at 18:32
  • That is pretty much the exact opposite from what the answers there say... – Telastyn Dec 04 '19 at 22:46
  • 15
    @D.BenKnoble exactly due to variety of formats in zip codes - or, more in general, in addresses - about which you don't know (and don't want to know) a thing, in 99.9% of applications you aren't going to need a complex class hierarchy for zipcodes, but you are best served by the exact opposite choice, i.e. a plain string without particular constraints. – Matteo Italia Dec 05 '19 at 00:19
  • 2
    Oh, are we bringing up weird addresses? Ok, [The European City Centre With No Street Names](https://www.youtube.com/watch?v=DSG-zxGRkJw). – Theraot Dec 05 '19 at 01:48
  • 1
    @MatteoItalia yes and no. Depends on whether you want/need to do validation of the postal code or not. If not, a flat string is perfect. If you do need validation you still store it as a flat string in your datastore, but need complex code to do the validation, different for each country (and sometimes region within a country). – jwenting Dec 05 '19 at 08:03
  • 2
    I would never use interfaces on domain entities. Just model your classes so they reflect your bussines objects, and if you believe something might be too complex to be represented by a simple type, create a class for it. I don't see how interfaces can be usefull on domain entities, and they are going to be troublesome when you try to serialize them so you can store your data or communicate with another piece of software. – Muscicapa Striata Dec 05 '19 at 09:04
  • 1
    @Theraot Actually they have names. The name is just called "A5" instead of "Bahnhofstrasse". On a conceptual level this is not any different to "Strasse 5", "Strasse A" or "2. Kapellenweg". It will mess up most address parsers which do not expect numbers as part of a street name. But for the representation as an object it has _no_ relevance – Manziel Dec 05 '19 at 09:52
  • @Manziel first of all, that is the the title of the video. With that said, my understanding is that they are block names, not street names. – Theraot Dec 05 '19 at 09:56
  • 2
    Well...for the purpose of navigation you are right. In this case a code refers to a block. For the purpose of a postal address they are just street names (actually street names are the abstraction to this). I can send a letter to A1 2 and it will be delievered to block A1 house number 2, only the local postal service has to care how to find it. This is by the way the correct handling of special data. If irregular naming does not have any relevance to your application, do not expose the irregularity. If you happen to be google maps, then you have to find a mapping for this – Manziel Dec 05 '19 at 10:15
  • 1
    I think all this discussion of zipcodes is completely missing the point. I certainly *do not* make every class member an interface variable. Unless you're a library author, you have the luxury of doing that only when you need to. – Mateen Ulhaq Dec 06 '19 at 03:31

7 Answers7

80

Programming to an interface means that you should focus on what the code does, not how it is actually implemented. See Telastyn's answer to Understanding “programming to an interface”. Interface classes help to enforce this guideline, but this does not mean that you should never use concrete classes.

I really like the zip code example:

In 1963, the United States Postal Service introduces zip codes that consist of five digits. You may now get the (bad) idea that an integer is a sufficient representation and use it throughout your code. 1983, a new zip code format is introduced. It uses 5 digits, a hyphen, and 4 other digits. Suddenly, zip codes are not integers anymore and you need to change all places that use integers for zip codes to something else, for example, to strings. This refactoring could have been avoided if a designated ZipCode class would have been used. Then, only the internal representation of the ZipCode class needed to be changed and its usages would have stayed the same.

Using a ZipCode class is already programming to an interface: Instead of programming against some concrete implementation ("integer" / "string"), you program against some abstraction ("zip code").

If necessary, you can add another level of abstraction: You may need to support different postal code formats for different countries. For example, you could add an interface IPostalCode with implementations like PostalCodeUS (which is just the ZipCode from above), PostalCodeIreland, PostalCodeNetherlands etc. However, too many levels of abstraction can also make the code more complex and harder to reason about and I think it depends on your application's requirements whether you want to use some ZipCode class or some IPostalCode interface. Maybe a ZipCode that internally uses a (unicode) string is good enough for all countries and country specific stuff, for example, validation, can be handled outside the class in some PostalCodeValidator / IPostalCodeValidator.

pschill
  • 1,807
  • 7
  • 13
  • 2
    In other words, using (the public *interface* of) `ZipCode` in the definition of `Address` is already *programming against an interface*. – WorldSEnder Dec 05 '19 at 15:08
  • 7
    It seems to me that appending warts like `I` to the names of types is a symptom of missing the point. It suggests that the interface is something on the side and the concrete type is what really matters. Your point here is exactly right. If you program to the public methods of a class, you should be able to turn that class into an interface if needed. Renaming the type by adding a wart just generates a lot of unnecessary work when the interface of the type did not change. – JimmyJames Dec 05 '19 at 16:27
  • Additionally the classes give you greater type safety so you'd see a compilation error when trying to pass a PostCodeUS into a function that only accepted PostCodeIreland whereas if both were just strings you wouldn't see a compilation error. – DavidTheWin Dec 05 '19 at 18:12
  • 2
    Interfaces that focus on "what the code does" are called "role interfaces". Do not confuse these with library interfaces that had no idea what your code would be. Not only do role interfaces make for flexible code, they clearly communicate what clients need from their services. Both `ZipCode` and `IZipCode` could be a role interface. However, `java.lang.Number` permits people with negative ages. At the very least, that's a very curious need. – candied_orange Dec 05 '19 at 21:19
  • 5
    I've never seen the point of a prefix/suffix for interfaces.  The only reason for calling an interface `IAddress` is if the `I` distinguishes it from an `Address` concrete implementation — which suggests you don't know _why_ you had the interface in the first place!  An implementation should be named for whatever distinguishes it (e.g. `USAddress` and `UKAddress`, or `StringBasedAddress` and `FieldBasedAddress`).  And you don't need a prefix/suffix at all.  Conversely, if you _can't_ see how implementations could possibly differ, then there's probably no need for an interface. – gidds Dec 06 '19 at 00:16
  • 1
    @gidds So how do you name your interfaces and classes if the need for the abstraction is solely for allowing you to mock the class in tests? – Voo Dec 06 '19 at 10:46
  • @Voo I've seen the pattern of `Foo` and `FooImpl` I'm not a huge fan of that either but it beats prepending all your types with `I`. – JimmyJames Dec 06 '19 at 17:18
  • @JimmyJames I think these unfortunate practices of prepending "I" or appending "Impl" are warts of the Enterprise Java Beans. It used to be very common back when EJBs were common. Of course, it's an anti-pattern, but like many such warts it became ingrained for a generation of Java programmers. And yes, it's completely missing the point :) – Andres F. Dec 06 '19 at 19:06
  • `only the internal representation of the ZipCode class needed to be changed and its usages would have stayed the same.` really? Input, output, validation, packing in a datagram ... – edc65 Dec 06 '19 at 19:59
  • 3
    @JimmyJames True. The `I` convention is a product of failed education that starts with classes and mentions interfaces almost as an afterthought. In languages that support `interface` types, interfaces are the first-class types. Methods should take interfaces as their arguments and return interfaces; callers seldom need to know the actual types they're working with. Hence the `Impl` convention is fine because nobody ever sees it. – StackOverthrow Dec 06 '19 at 21:07
  • Of course you should just regard the address as an opaque blob, as there are too many ways it could be formed, all of them right for some specific cases, and let the interested parties worry about isolating a specific detail, where and when that is interesting, and actually exists. – Deduplicator Dec 07 '19 at 02:17
48

"Programming to an interface" does not require the language keyword interface. It means you care about what promises the type provides about it's behaviour.

You don't care how java.lang.String does all the things it does, only that you can write

name = "aacceeggiikk";
age = 42;

"Programming to an implementation" would be using reflection to get at the private members of String, and fiddling about with them. And then being upset that an update broke your code because you were relying on something that was changed.

Caleth
  • 10,519
  • 2
  • 23
  • 35
13

It's only a guideline

Just as a concept like KISS is a guideline.

If you follow the guideline to "program to an interface, not an implementation", you'd be violating the KISS principle. But by making a bunch of simple classes that repeat some code or actions, you'd be violating the DRY principle.

There's no way to adhere to every single guideline, principle or "rule" out there. You need to find out what balance makes sense for your application.

My own experience is that trying to make everything enterprise proof with abstracted layers on top of abstracted layers, is often overkill, and unnecessarily complex for many applications.

Ivo Coumans
  • 464
  • 3
  • 7
7
  1. Programming to an interface concerns objects, entities that can also have primitive-typed properties like boolean, int, BigDecimal and String, to name some. On that level programming to an interface no longer applies.

  2. There is an other principal however, that is based on the experience that primitive types are used where there should be a wrapper to a more abstract value class. So instead

    String telephone;
    

    you might better have a value class Telephone:

    Telephone telephone.
    

    This can take care of country number, canonical form, equality and such. Again Telephone could be an interface and there could be an implementing class TelephoneImpl, but I - fortunately - seldom see this.

    Even better is when more than one primitive fields can be replaced by a more abstract class, like FullName, say with int similarityPercentage(FullName other).

  3. Maybe it should be mentioned that internal objects with internal implementations can utilize interfaces. A List has an interface Iterator iterator() implementing a local object with access to the list container. But here interface is part of the solution, not coding style.

Succinctly:

Programming by interface decouples the implementation class, allows several implementations, has a single responsibility as API. It concerns behavior, methods. Take JDBC as example.

Fields are concrete properties. They are data. They should also be abstracted, hiding implementation by using a class name and provide access methods, for a correct usage. However that is a different level. Take the classes LocalDate, LocalDateTime, YearMonth instead of the old Date for all.

Joop Eggen
  • 2,011
  • 12
  • 10
7

To quote a colleague of mine - "You can spend hours designing a car on a computer, but at some point, the wheels actually have to touch the ground.".

i.e. not everything can/should be abstracted out, at some point you need a concrete type.

For your class with your zipcode, the question really should be what is a zipcode (in terms of your application)?

Answer - it's a string, so the property should be a string.

public class Address {
     public String ZipCode...
}

P.S. don't start getting into abstracting out the underlying language types - this way lies madness!

There are a number of answers/comments here commenting on the variety of Zip/postal codes in various countries and that is true, however, they are all ultimately strings.

Hopefully this Address class does not care what is in the Zipcode (just that it is a string) and there is another class that you use to validate your Address (and being able to switch out the implementation of that class may well be useful).

So, program to an interface where it makes sense to do so and particularly where the implementation may need to be altered or behaviour injected. Do not use an interface for everything!

Paddy
  • 2,623
  • 16
  • 17
1

In your Address/ZipCode example:

public class Address {
private ZipCode zipcode;
}

Tying yourself to a concrete implementation instead of an Interface leaves with 3 problems:

  • A - Internationalization - An e.g. Canadian Postal Code is something like V1C3X8, other countries addresses are different again. Good luck storing your customer's billing addresses in a globalized economy.
  • B - A change in implementation, which is why everything should be consumed as an interface - You might decide one day that it's more efficient to store Addresses as a What Three Words GeoCode stored as a number and a floor/unit number for multi-story building. Therefore Address could be be changed from something like this:
public class Address : IAddress 
{
    public IZipCode ZipCode{get; private set;}
    public string StreetName{get;private set}
}

To something like:

public class Address : IAddress 
{
    public Address(IGeoCodeService geoCodeService/*Dependency injected*/){........}

    private long What3WordsGeoCodeAsLong {get; set;}

    public IZipCode ZipCode => GetZipCodeFromGeoCode();//calls out to a service to 
    public string StreetName => GetStreetNameFromGeoCode();
}

When serialized, the second implementation is much smaller to store, and immune to being out-dated if some country decides to move Postal Code areas around, which happens more often than you think (with a trade-off of having to use a service to look up a Postal Code each time.), while anything that uses IAddress will not even be "aware" that the implementation is now completely different!

  • C - Unit testing, I see that you're using a .Net naming convention. I am not aware of any .Net testing framework that will allow you to mock the methods on Address and ZipCode in any classes where they are used, you need to consume them as IAddress and IZipcode.
Eugene
  • 129
  • 3
0

This is my take on "programming to an interface". There are two pieces to it: inputs and outputs.

Inputs

I differentiate between objects that are data and objects that have behavior, especially side effects.

In both of your examples, I'd use the concrete classes: strings and zip codes are data, IMO. (I would NOT just make the ZipCode a String, as one of the answers suggested!!! You cannot just pass any old string and treat it like a ZipCode. Your ZipCode class should have different constructors for the different kinds of zip codes you might deal with. Interally you might store a string, but at least do validation in the constructors!).

However, if I needed an object that did "computing" of some kind (a vague term, I know), I'd use an interface. For example, if I needed an object that allowed me to search for Students by name, I'd take an interface for input. The interface would be something like:

interface StudentSearchInterface {
    Optional<Student> search(String query);
}

The reason this is a good idea is because you can swap out providers later, and also it allows you to write much better tests where you can code up "fake" StudentSearchInterface objects that return exactly what you want for your specific test. It might be hard to test the production class that implements StudentSearchInterface: what if it queries a server on the internet? Or reads from a database? The results might change between calls to search(), so you can't really test it.

Outputs

Your return values should either be "data" or an Interface as well. By data, I mean a class that really only has getters and/or setters and maybe some trivial methods like toString(), etc. Otherwise, do an interface. A common example is returning a List<Foo> instead of an ArrayList<Foo>. This gives you the freedom to change the logic inside your method to use something else that implements List instead of being stuck with a specific List implementer because that's what you used first.

Just like we mark some methods and fields as private to encapsulate them, we should use interfaces to only provide info on a "need to know" basis.

R. Agnese
  • 101
  • 1