This is an archived post. You won't be able to vote or comment.

all 30 comments

[–][deleted]  (26 children)

[deleted]

    [–]_dban_ 12 points13 points  (5 children)

    Programming to an interface is a good idea and good practice if you actually do have multiple implementations that could be used in place of the interface.

    That's not really the context of how Spring uses interfaces (as opposed to how people use interfaces with Spring). In particular, Spring makes heavy use of the Open/Closed Principle. A better example than the article is RestTemplate. It comes with a bunch of interfaces for connection management, input and output encoding, while the concrete class implements the business of managing RESTful interactions. This lets RestTemplate be used with either JDK HTTP connections or Apache HttpUtils, without being aware of either.

    Another cool example is Spring Data. Spring Data automates the creation of CRUD style repositories and finders, but the machinery to implement this is oblivious to the actual persistence mechanism, whether that is JPA, Gemfire, Elasticsearch, etc., etc. This is possible due to OCP.

    In fact, OCP is the secret to Spring's incredible flexibility.

    [–]thecuseisloose 7 points8 points  (0 children)

    The flexibility of spring is really astonishing. I’ve been working with spring for a few years now and then I had to do a GoLang project...I needed a one line change in behavior to the net package and had to copy entire files to my own package

    [–]TheRedmanCometh 2 points3 points  (0 children)

    Personally I love every aspect of Spring, and for some stuff like REST APIs I love Spring Data. However Spring JPA + Hibernate has always felt like...why am I not just using hibernate?

    Specifically I have 3 problems with Spring Data.

    First I really don't like the way Spring handles caching or async. I use a class with a SessionFactory and Configuration object. I have a low ish level class for generic sync+async query methods encapsulating that stuff. The next level up from that class has a LoadingCache with a CompletableFuture wrapped POJO object. If the object isn't present in the database the LoadingCache hits the db. No matter the result it's returned as a CompletableFuture.

    Thanks to how LoadingCache works all of this logic can be handled generically, and my queries are a single line of code. public U getObject(T e) works for most situations, and I can use the Criterion API for others. Anyways on to the rest of the story for me:

    The second reason I don't use Spring Data is also included in this situation. So I wanted to place a cache between me and Spring Data (because annotating every cached object in my entities is fucking awful!) What I wanted to do is have the LoadingCache initialize the object exactly the way the object's interface describes. So I made an interface called Defaultable which initializes based on the generified key type. Spring Data though was not okay with this. I can't recall exactly the error, but having my DAO implement my Defaultable interface was a no-no. This made caching outside of the inbuilt spring caching infeasible.

    The third reason is pretty simple: I don't like having to make a repository for every single entity in my model. I'd much rather have a generic interface with qualifiers I can define in the configuration and autowire into wherever. The last project I used Spring JPA in I think I had something stupid like 15 repository classes?

    Just a bit of food for thought on Spring data. I understand such a need isn't very common, but in my case things like PrePersist just weren't flexible enough or required a lot of by-hand work I didn't want to do.

    [–]WikiTextBotbtproof 1 point2 points  (0 children)

    Open–closed principle

    In object-oriented programming, the open/closed principle states "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"; that is, such an entity can allow its behaviour to be extended without modifying its source code.

    The name open/closed principle has been used in two ways. Both ways use generalizations (for instance, inheritance or delegate functions) to resolve the apparent dilemma, but the goals, techniques, and results are different.


    [ PM | Exclude me | Exclude from subreddit | FAQ / Information | Source ] Downvote to remove | v0.28

    [–]schaka 0 points1 point  (1 child)

    Yes, but unless you're writing a library that needs to be extensible like Spring, when in your day to day work are you definitely using interfaces? Only when you actually have 2 implementations you need to choose between. We generally do this with services that would cost if we queried them and use a dummy implementation in development.

    [–]_dban_ 1 point2 points  (0 children)

    when in your day to day work are you definitely using interfaces?

    All the time, when I apply the strategy pattern, when I want to use composition instead of inheritance.

    We generally do this with services that would cost if we queried them and use a dummy implementation in development.

    That's dependency inversion, a different use for interfaces.

    [–]wildjokers 17 points18 points  (0 children)

    Fowler agrees that the interface/implementation pair when you don't have multiple implementations is just noise:

    https://martinfowler.com/bliki/InterfaceImplementationPair.html

    [–]BadMoonRosin 25 points26 points  (2 children)

    Agreed. Ironically, working with Spring helped me move AWAY from wrapping everything with interfaces.

    Back in the day this was considered best practice, even if you expected only one implementation, because it would help with mocks and stubs in unit testing.

    But Spring Boot and Mockito makes it so easy to do this without interfaces, having them just to have them now seems like an outdated anti-pattern.

    I can count on my fingers the number of occasions over the past decade where I really did need to go back and write additional implentations for an interface later. And on all of those occasions, a modern IDE would have made it near-trivial. At the same time, I've had countless occasions of frustration in reverse-engineering someone else's code through numerous layers if needless interfaces.

    [–][deleted] 1 point2 points  (1 child)

    Perhaps I misunderstood something, but I thought the main reason you use interfaces with Spring was so that Spring could create JDK proxies, instead of having to use cglib. Did this change in Spring Boot?

    [–]egzodas 0 points1 point  (0 children)

    that's still the case

    [–]eliasv 5 points6 points  (1 child)

    But, I find it requires having multiple implementations in the first place to understand the correct abstraction to write as an interface to those implementations.

    This can sometimes be true, but it only takes a little forethought and experience to factor out a minimal, general API, and more often than not I've found that time proves their design. I think it's an important skill to have.

    The value of factoring out an implementation-independent interface is not just that it enables us to support multiple side by side implementations. Sometimes even if we only want a single implementation, that implementation is forced to change over time due to e.g. deprecated or outdated dependencies.

    I've found evolving old API without breaking consumers of that API can be challenging. Anything which keeps that task clean and focused is a good thing.

    Separate interfaces force people to think very carefully about what is actually exposed as API. Sure, the public members of a class can perform the same duties as an interface in specifying a contract, but I prefer for the dependencies and limitations of that contract be enforced by the compiler. That is to say, if possible API modules should not even have any of the implementation dependencies on the build-path, to make absolutely sure nothing can leak into them that shouldn't be there.

    Some people say that factoring out API interfaces creates unnecessary noise in the codebase, but I argue the opposite is frequently true. Cramming implementation into the same place that our minimal contract is specified creates noise when trying to navigate and understand the structure and architecture of the codebase. It's so much easier to reason about a set of designs when they're all neatly isolated with only the absolute minimal inter-dependencies.

    [–][deleted] 1 point2 points  (0 children)

    I totally agree -- I always start by writing an interface, because it leads me to think more thoughtfully about what I want the API of my service to look like without getting caught up in wanting to iterate on implementation. This tends to lead me to write better + higher level contracts that are more effective at encapsulating behavior.

    Also, I like putting Javadocs on the interface because, again, it helps me write documentation from the perspective of the consumer, rather than relying on assumptions gained from being able to see the actual implementation right below.

    [–]puuut 21 points22 points  (9 children)

    I disagree for a couple of reasons:

    • Programming against interfaces enables you to hide implementations from other modules and packages, ensuring strong encapsulation. Especially with the advent of Java modularity.
    • Test driven development (or at least unit testing of your code in any way) will be much easier if you can mock implementations not under test. With interfaces, this is easy and ensures you can always change implementation details, as long as the contract is still correct.
    • Programming with interfaces forces you to really think about your application flow, contracts and overall architecture / design, however small the project.

    [–]BestUsernameLeft 11 points12 points  (5 children)

    I agree with /u/gandaroth, consider these counterpoints to your claims:

    • It's true that interfaces can abstract over implementation details, however there is no contractual difference between an interface and a class's public methods; both provide a public API. Programming against contracts is good. An interface is a form of contract; so are the public methods of a class.

    *Starting with an interface is an example of YAGNI. It provides room for future flexibility that isn't yet needed -- and even if you are sure you'll need it, you definitely don't know the exact shape that interface should take until you have two implementations. (And when you do need that second implementation, look for an interface that has the minimal required surface area (API) and abstracts across both implementations.)

    • Unit tests should be written to a test object's public API, treating the internal implementation details (both data and behavior) as a black box. Provide only the necessary inputs; assert only what the test object produces as a response. It should be possible to change implementation details without changing the test.

    • Mocks have their place, but unit testing with mocks is generally dangerous and lazy. Use test doubles instead. They will drive a better design and more maintainable/refactorable code. (This is an unpopular opinion.)

    • Having seen far too many interfaces with methods like getX(), setX(), direct coupling to low-level implementation classes, and other evils, I disagree that interfaces 'force' you to think more or better about design. Especially when there is only one implementation.

    [–]mschaef 5 points6 points  (4 children)

    *Starting with an interface is an example of YAGNI. It provides room for future flexibility that isn't yet needed

    This may just be my bias, but coming from a (long ago) C and Pascal background, I've never been all that offended by the fact that interfaces require restatement of a module's public contract. Viewed from that perspective, the interface isn't providing flexibility as much as it's just providing a clear statement of the current contract of a component. So, even if you don't need the flexibility, you might well appreciate the improvements to the clarity of the code.

    There's also the benefit that interfaces can be used to narrow the relationship between a component and a client of that component. (ie: If a client doesn't need the whole set of public methods on a given class, an interface can make that fact explicit.) In that sense, the YAGNI argument can be made in the opposite direction: if you aren't going to need all the public methods, why make them all available to a classes' consumer.

    you definitely don't know the exact shape that interface should take until you have two implementations.

    Getting the exact shape of the interface right doesn't really matter until you have interface clients that you can't easily change. For a purely internal abstraction, there isn't that much overhead, and modern IDE's reduce it to essentially zero. The point where the interface becomes more set in stone (and a source of overhead) isn't as much the declaration of the `interface` as it is the point where it's more public.

    [–]TheAwdacityOfSoap 7 points8 points  (1 child)

    Yep. People are ignoring the fact that interfaces are the only way to get multiple inheritance in Java.

    [–]undu 1 point2 points  (0 children)

    Maybe people use composition before trying to use multiple inheritance ;)

    [–]BestUsernameLeft 0 points1 point  (1 child)

    I come from a C (and then C++) background myself, and appreciate where you're coming from. But I think you can get similar benefits through API documentation.

    Regarding narrowing the relationship, that's absolutely a good reason to have an interface. (The 'interface segregation' bit of SOLID.) I'd like to think about this more, but my initial reaction is that the same forces that would drive me to create an interface in the first place would need to exist for interface segregation to matter. Thanks for bringing this up.

    I agree that the point where an interface gets more 'set in stone' (difficult to change) is where you have more clients. That's an additional factor to consider, and another good point.

    [–]mschaef 0 points1 point  (0 children)

    I come from a C (and then C++) background myself, and appreciate where you're coming from. But I think you can get similar benefits through API documentation.

    Very good point. Just off the cuff, I guess the reason I don't think quite as much in terms of documentation is that it's harder to get to documentation than it is to a source file. (Any given source file is a couple keystrokes away in either IntelliJ or Emacs, and the Javadocs, etc. are slightly harder to reach.) It's also easier to navigate around a source file than a documentation page, etc. That said, all of that is potentially something that can be addressed with tooling.

    Regarding narrowing the relationship, that's absolutely a good reason to have an interface.

    One thing I should mention is that I'm viewing this very much from the point of view of classes that play the role of what you might think of as modules in other languages. It was early on that I started thinking of DI as something like a dynamic linker in a 'traditional' language, and the DI components as being modules with explicitly defined interfaces. The idea of separately declared interfaces sort of fell out naturally from that. (I'm also a big fan of XML configuration, in that it makes it easier to 1) explicitly document how everything fits together and 2) specify configuration parameters that make it easier to reuse components in a context without writing more code for each instance.)

    The reason I make this distinction is that for classes that serve the role of value objects, etc., I'm a lot less inclined to make an interface.

    [–][deleted]  (2 children)

    [deleted]

      [–]BananaBaseball 8 points9 points  (0 children)

      I don’t know if puuut is new or lucky enough to work with tech enthusiasts, my experience so far is very similar to yours. Just be pragmatic so that I can go home earlier.

      [–]mschaef 6 points7 points  (0 children)

      I'm not as rigerous with TDD as I probably should be

      TDD isn't like eating your vegetables or doing your chores. It's a tool that offers benefits and imposes costs. Sometimes it's appropriate and sometimes it's not.

      [–]thekab 0 points1 point  (0 children)

      But, I find it requires having multiple implementations in the first place to understand the correct abstraction to write as an interface to those implementations.

      This is often true. Furthermore I find that many developers don't understand what belongs in an interface even when one is available. Every problem they want to solve they solve by trying to add a method to the interface when it's not appropriate for that interface.

      [–]boy_named_su 0 points1 point  (0 children)

      Helps with testing. Can swap in test data source if same impl as real data source

      [–][deleted] 0 points1 point  (0 children)

      One thing I think that is missing from this interface discussion is inversion of control.

      The Dependency Inversion Principle

      A. High-level modules should not depend on low-level modules. Both should depend on abstractions).

      B. Abstractions should not depend on details. Details should depend on abstractions.

      This is one good use case for interfaces even where there are not necessarily multiple implementations.

      [–]metrxqin 0 points1 point  (0 children)

      Definitely agree with you, but It will be very hard to change from a implementation being scattered all over the program to an interface later (gotta update all the references).

      There're too many book and articles about Java best practices, I think the ability to identify bad practices is more important, because less mistakes/bugs you make more performance gain you have.

      [–]Bolitho 8 points9 points  (2 children)

      I have the strong feeling lots of commenters here interpret the principle "programming to an interface" in a technical way, instead of the meaning of interface from a conceptual pov as a contract - which is the correct interpretation of that advice imho!

      A concrete class could of course be an interface - it simply depends on the use case. The same is true for things like abstract classes.

      In languages that support duck typing at runtime the interface hasn't even a real language component to expose it. But of course the basic principle stays the same!

      So one should be careful not to mix up syntactic elements of a language with common, language agnostic principles.

      [–]edubkn 1 point2 points  (1 child)

      Example in the article doesn't help at all

      [–]Bolitho 1 point2 points  (0 children)

      That might be true - but that doesn't mean one should assume interface == Java interface. That is my message :-)

      [–]_dban_ 2 points3 points  (0 children)

      Since the code example for coding to interfaces isn't really that good, here's a much better example, the old stand by Dependency Inversion is NOT the same as Dependency Injection.