2

I read an article about that using an interface for an entity is an anti-pattern for these reasons:

  • Your interface signature is identical to your class.
  • There’s only one implementation of your interface.
  • Your entity has no behavior to abstract, yet you’ve created an abstraction anyway.

But if I have multiple entity implementations, like this:

interface Book {
  String getId();
  String getName();
// other methods to access entity values

}
class JpaBook implements Book {
  // implement entity for JPA
}
class RedisBook implements Book {
  // implement entity for Redis(for caching)
}

Is that good? Or should it be avoided? Also, when I do it like this, I need to create an abstraction between service and repository to create proper Book implementations (redis or jpa, for example) and DAO code becomes complicated.

Does any pattern exist to solve my problem?

If my question is not clear, I can provide more info about it. Thanks!

9
  • 6
    "Anti-pattern" is a big word. If you don't have and can't imagine having different subtypes of a type, defining an interface s simply unnecessary. Certainly it's not going to improve your code base just by the magic power of being an interface. Sep 11, 2023 at 14:25
  • 3
    The answer on whether this is good or bad depends a lot on the particularities of the project. I have seen situations in which such use of interface is good and I have seen situations in which it is just a maintenance overhead.
    – Ccm
    Sep 11, 2023 at 15:22
  • 5
    "There’s only one implementation of your interface" you show an example of two implementations of a single interface. What does this example have to do with the question?
    – JimmyJames
    Sep 11, 2023 at 15:25
  • 2
    Yes, and this example doesn't meet that condition.
    – JimmyJames
    Sep 11, 2023 at 15:31
  • 2
    Compare the sentences "There’s only one implementation of your interface" and "My example shows if I have multiple entities implementations" How the second doesn't fit with the first?
    – JimmyJames
    Sep 11, 2023 at 15:38

9 Answers 9

9

Without the full context of this article, it's impossible to say exactly what the point of it was but I think I can provide a useful answer to your question.

The first bullet "Your interface signature identical to your class" is a fairly weak argument. An interface doesn't need to match the signature of class (or vice-versa) but if it does, that isn't necessarily a problem.

As I noted in the question comments, your example doesn't align with the second bullet: "There’s only one implementation of your interface". If you have two or more implementations of an interface, it's a hint that the interface exists for good reason. It's not guaranteed, though.

The third bullet kind of sounds good but doesn't really hold up under scrutiny. I think your example demonstrates why.

Instead of trying to guess what the author might be getting at and whether that is right or wrong, I'll explain the interface anti-pattern that your question brings to mind: creating an interface for every class (or almost every class.) There are two main reasons that I think this is a bad practice.

The first is that it means there are going to be many interfaces which have no reason to exist and simply create additional maintenance burden. As someone once said, writing the same thing twice (or three times) doesn't make it correct. Having an interface which simply restates the interface of a single class is pointless. This aligns to the first bullet and may be what the author was getting at.

More importantly, in my opinion, is that such an approach completely misses the point of interfaces. It's thinking about them from the wrong direction. This is a fundament mistake that I think a lot of OO developers make. That is, they write a class or set of classes and then try to align interfaces to match the class definitions. Instead, interfaces should be designed from the perspective of the client of that interface and classes should be written to align with those interfaces as needed.

For example, your Book interface is presumably used in the signature of some method or constructor either as a parameter or a return type. There are then dependencies on that specification in the code. (If that's not the case, then there's no reason to define it.) If you didn't have the interface, you would need to create and maintain duplicate versions of the same functionality for each type. That's a problem.

I argue that the best way to define interfaces is to create them in the code that will call them. With any decent IDE, you can create an empty interface and then write the code that will interact with it. As you do this, you will start calling methods that aren't defined. Your IDE will point this out and then offer to create the definition for you. Check that it did what you wanted and repeat this until you are done with the calling code. You will then have an interface which specifies the behaviors that it needs to support. Worry about creating/conforming the implementation(s) that match that interface when it comes time to pass instances in or return instances of them. Note that this doesn't mean that you should have a separate interface for every parameter of every method. The concept of a Book and its properties/behaviors should be common across code that works with books. These interface definitions are the core aspect of the OO design work.

7
  • 7
    "I argue that the best way to define interfaces is to create them in the code that will call them." — perfectly said! Interfaces exist to serve the caller, not the implementing class. Sep 11, 2023 at 23:50
  • @GregBurghardt if only it were that simple. Yes all too often Interfaces are designed while only considering what they implement. But reversing that into another absolute is wrong as well. Consider this example that does both. Sep 12, 2023 at 3:27
  • @candied_orange I'm honestly not completely sure what you are getting at but in my mind 'ownership' is a distinct concept from 'design'. Of course there are reasons you an interface needs to change or be a certain way due to it's implementation. But interfaces are inherently client-centric. The entire purpose of their existence is to define how callers can interact with classes.
    – JimmyJames
    Sep 12, 2023 at 14:28
  • @JimmyJames the right question to ask is which gets to dictate change. It's not always implementers. But it's also not always callers. That's what that example shows. Sep 12, 2023 at 14:37
  • 2
    How do you do dependency injection without interfaces? How do you unit test you code without it? Sep 12, 2023 at 15:24
6

Interfaces should be designed in their own right. They are not simply accessories to a class.

That said, this:

Your interface signature [is] identical to your class

can actually be fine. It just should never be the brain dead default interface you thoughtlessly exact from every class you design. Just because a refactoring exists that lets you do this with one click doesn’t mean this is the thing to do. Just because you can easily slap an I on every name doesn’t mean you should.

But just because an Interface happens to cover a classes interface 100% doesn’t make it bad. Just suspicious.

An interface is its own thing. It exists for its own reasons. Where things go wrong is when that is ignored.

There’s only one implementation of your interface

Interfaces work fine with one implementation. This rule only makes sense if your only justification for having the Interface is polymorphism. There are other justifications.

Your entity has no behavior to abstract, yet you’ve created an abstraction anyway

Well abstraction comes in many forms. Ever hear of the OSI stack? All those headers are abstractions. Not a single behavior to be found here. Yet a full packet has many headers that mean different things to different layers that could be modeled with interfaces. But this could also be done with data structures so meh.

I haven’t read whatever article you’re talking about but I’ve seen the bad habits that might inspire these rules. But every one of them has reasonable exceptions. So don’t get in the bad habit of following them blindly.

I mentioned that sometimes an Interface exists for other reasons. One of those reasons is because a constructor or method wants to express its needs (dependencies) without being opinionated about how they are met (hard coding an implementation). In these cases there may be zero implementations, yet.

The benefits of not hard coding can be many things. Controlling what direction your static dependencies point. Not needing to know what another team is coding before you can code. And not yet knowing what you will code here yourself. What knows about what is the most profound design question. This lets you control that.

Another of those reasons is maybe you only need access to some of a classes methods. Why pretend you need more than you do? This idea has been given the name Interface Segregation Principal. Interfaces let you support this idea as well.

There are others like supporting multiple inheritance but that’s a bit language specific.

All that said, there are also benefits to having no Interface, no indirection, and knowing exactly what you’re working with.

As for your code example, that looks like polymorphism centered around persistence, which is a behavior. So if there’s something wrong with it I don’t see it.

4
  • Thanks for your reply! Entity as interface also lead a lot of code(like I mention before in question I need to create another abstraction between service and repository layers to map entity to proper implementation). Is it okay to do it like this by introducing additional layer between service and repo?
    – Mercury
    Sep 11, 2023 at 15:21
  • "One of those reasons is because a constructor or method wants to express its needs (dependencies) without being opinionated about how they are met": I'm not seeing how this is a different justification than polymorphism.
    – JimmyJames
    Sep 11, 2023 at 15:28
  • To allow the definition of more forms. What other point would there be? Enabling the use of polymorphism is a justification based on polymorphism.
    – JimmyJames
    Sep 11, 2023 at 15:36
  • @JimmyJames better now? Sep 11, 2023 at 16:10
4

Put it to the test:

class JpaBook implements Book {
  // implement entity for JPA
}
class RedisBook implements Book {
  // implement entity for Redis(for caching)
}

If you delete implements Book and the interface Book from your code, maybe replacing the interface by concrete types in your implementations, does it still work?

If it still works, it was unneccessary abstraction. It is "Waste", it had to be written, maintainted, tested, all for nothing. It should go.

If your application doesn't work without it, then obviously it is not an anti-pattern, it is neccessary.

4
  • 13
    "If your application doesn't work without it, then obviously it is not an anti-pattern, it is neccessary." This is tests necessity of the pattern, but I don't think it tests whether it's an anti-pattern. Consider what happens if I delete a global variable. All the references to it in my app will stop works. Am I to conclude that global variables aren't an anti-pattern, just because they're necessary to that version of my app?
    – Alexander
    Sep 12, 2023 at 0:05
  • 3
    There is a certain wisdom here. But also a subtle trap. Be careful how you define “works”. After all, you can compile, delete all your source code, run the binary, and say, “look, it works” Sep 12, 2023 at 1:58
  • 1
    @Alexander: nvoigt's answer does not claim that "can the application work without it" is a perfect test for every possible pattern or part of your codebase. But it is a correct test for the question that is being asked here. Your comment is not wrong about its content but you're attacking a point that was never being made in the first place.
    – Flater
    Sep 12, 2023 at 2:34
  • 1
    @Flater I definitely don’t want to be mischaracterizing his statement and attack a straw man, but I really do think his answer (as currently written) is setting this up as a rule-of-thumb for identifying an anti pattern. And if it’s not that, I don’t know what else it could possibly be instead.
    – Alexander
    Sep 12, 2023 at 4:02
0

Two counterarguments:

  1. quite often, there is a second implementation: a mock object for unit testing.
  2. even if there isn't, it is a stronger split between interface and implementation.
0

tl;dr: interfaces are for abstraction, interfaces aren't for polymorphism nor for inheritance

I have to admit I read anti-pattern definition before start thinking about when to use interface.

An anti-pattern in software engineering, project management, and business processes is a common response to a recurring problem that is usually ineffective and risks being highly counterproductive.

Interface used for entity's polymorphism might seem to match the definition of anti-pattern. With interface used to abstract entities, that is the case from description, is different:

Also, when do it like this, I need to create an abstraction between service and repository to create proper Book impl(redis or jpa, for example) and DAO code become complicated.

That implies the service library is developed and to build up an application other libraries have to be implemented, so the use of interface is to expose the required model to have a functional service.

The answer to

Does any pattern exist to solve my problem?

could be inferred from the above details, abstract entities using interfaces when the service library is developed without knowledge about additional libraries.

0

I read an article about that using interface for entity is anti-pattern by these reasons

You need to understand this advice in context.

Programming to interface is a very well known and important pattern. Besides allowing polymorphism and mocking, programming to interface it also lets us to make our code cleaner, more flexible and easier to understand and refactor by decoupling code that uses a particular functionality (encapsulated by an interface) from the code that implements it.

However, like any good thing, it can be taken to an excess. When blindly followed by overenthusiastic people who've heard about how good programming to interface is, but who haven't fully understood why and when it is so good (and when it isn't), this leads to silly rules like "every class must have a corresponding interface type" or "all method arguments and local variables must have interface types" that just end up cluttering the codebase with redundant interfaces that add no useful abstraction over the classes that implement them.

The advice you've read is a counterreaction to this. It's saying that creating an interface just to wrap a single concrete class is (usually) a bad idea in cases where:

  1. there is only one class that implements the interface (so that having the interface doesn't help with polymorphism),
  2. the interface is identical to the public methods of the class (so that having the interface doesn't hide any implementation complexity from the code using it), and
  3. there is no other reason to create and use an extra abstraction layer around the class.

Basically, it's just saying that you should avoid creating an interface if the interface does nothing useful for you (or for anyone else using or working on the code). Which should be obvious, really.


What about your Book example, then? Well, there you already have two classes implementing the interface, so condition #1 above doesn't hold — the interface does do something useful by enabling polymorphism.

(In Java, it might be better to make Book an abstract parent class of JpaBook and RedisBook and any other concrete book classes you might end up having. But this is mostly just a technical implementation detail — the difference between a Java interface and an abstract parent class is a hairsplittingly fine one, and basically invisible from the point of view of code actually using the class/interface.)


Could you implement the same functionality using just a single concrete Book class with no subclasses? Probably yes. The bad and ugly way would be to just include both the JPA and Redis implementation code in the same class and choose between them at runtime, but I'm sure you understand why that would be a bad idea.

A better approach might be to have your (concrete) Book class hold an instance of an adapter class that encapsulates the JPA / Redis specific backend functionality in a common API, and delegate any backend functionality that requires a polymorphic implementation to the adapter. That way, you code would look something like:

class Book {
  private final DataStoreAdapter adapter;
  // constructor and methods omitted
}

abstract class DataStoreAdapter {
  // this could also be an interface if you wanted
}

class RedisAdapter extends DataStoreAdapter {
  // implement Redis-specific functionality here
}

class JpaAdapter extends DataStoreAdapter {
  // implement JPA-specific functionality here
}

Of course, this is really just kicking the ball one step further: you still have an interface (or an abstract class) with two concrete implementations for polymorphism, now the polymorphism just happens at the adapter level instead of at the Book level.

Is it better? Maybe. It all depends on whether the (now datastore-agnostic) Book class has enough non-trivial functionality left after refactoring all the datastore-specific stuff into the adapters. If it does, this may be a very good pattern to follow. If it doesn't, but just ends up delegating everything to the adapters, then using this pattern just complicates your code for no good reason (just like excessive use of unnecessary interfaces does!).

As with everything else in life and in programming, moderation is the key here, too.

0

The article you mention probably - more or less explicitly - places itself in the context of a Rich Domain model, or at least an architectural style where there is a "pure" business layer separated from other aspects like persistence, logging etc. and where entity classes combine data and behavior.

  • In that context, the anti-pattern warning makes sense because the only behavior contained in these entities is supposed to be business behavior, and doesn't often need to be completely reimplemented in different ways. You could need some polymorphism but there will remain a core common behavior to all variants of an entity. For example, there is no reason to change the domain behavior completely between Book and ForeignLanguageBook, so abstract classes that allow some logic sharing will be preferred to interfaces most of the time. Wanting to tack an interface on top of these entities is usually a sign that some infrastructural behavior will interfere.

  • In other contexts, like one that would use the Active Record pattern for data manipulation and logicless ("anemic") entities for instance, using interfaces like the ones in your example might make more sense.

0

Although not immediately obvious, your example demonstrates a common interface that represents the same conceptual data in two different domains.

This is one of the primary reasons to implement interfaces for entities.

  • JpaBook: DAL definition of a Book, likely to encapsulate the business logic related to the Book.
  • RedisBook: Redis Cache definition of a Book, likely to only include serialization logic and state information, no functional operations and certainly no DAL logic.

Without seeing the implementations, we can only make assumptions on this, consider this an educated guess based on OPs naming convention.

Conceptually this is a great case for using an interface, especially if your JpaBook is Self Tracking or has a lot of private or internal references to the context used in the DAL that would not be automatically re-created or re-established when you deserialize the object from the cache.

The RedisBook is effectively a DTO (Data Transfer Object) representation of JpaBook. Even if caching is less about transfer and more about staying in one place, but it is still a mechanism to transfer the data across the domain of time ;)

Even if these two classes exist in the same library or are both used interchangeably, the state where the object is serialized into Redis is an entirely different conceptual domain, we've gone from in-memory objects to text based storage.

By using the Book interface, we can write common logic that will work in either domain, or logic that will work regardless of where the Book instance originated from, with caching being an important concept in all production grade software, this is a scenario that we all might come across.

  • This specific scenario of defining and mapping DAL types to DTOs is itself almost considered an anti-pattern by some and a key argument for implementing POCO (Plain Old CLR Object) classes separate to the DAL context.

Without making the assumption about the domains, your example is not really detailed enough to prove or disprove if this is an anti-pattern or not. To do that we would need to analyse the implementation which you have specifically omitted.

The usual indicator of this being an anti-pattern is if the implementations are similar or identical. If they are similar, then you might benefit from a common base class, so from Inheritance over Composition.

If the implementations are identical, except for the values then this indicates that you might have confused types and instances.

If you haven't confused types and instances... when the structure and behaviour of the two classes is the same, I would argue that you have fallen into the classic OOP beginner trap of mistaking Categorization for Classification. If that is the case then you might find that adding a property to the class to define the Category would be simpler than duplicating the entire class to represent each Category.

This can be explained using colors of books:

interface Book {
  String getId();
  String getName();
// other methods to access entity values

}
class GreenBook implements Book {
  // implementation...
}
class BlueBook implements Book {
  // same implementation
}

There is no structural or behavioural difference between GreenBook and BlueBook, by this definition they are both Book instances, the only difference is in a value concept that your model doesn't yet describe. If the only difference between these two types of books is in their name, then refactor this out as a category or property of the Book type:

class Book {
  String getId();
  String getName();
  String getColor();
// other methods to access entity values
}
0

If you have a simple data-only object, declaring and using an interface costs very little.

Very often you don’t have a simple data object. Say you have an object that can persist or load itself. Or convert itself from/to JSON and you don’t care about this at all. You then declare an interface without these features and use that. The user of the interface cannot use features of the class they shouldn’t use. Without code change.

Not the answer you're looking for? Browse other questions tagged or ask your own question.