2

Sometimes, you inherit from a class that defines the semantics of your type (ShapeEllipseCircle). Other times, you inherit simply because it's convenient to do so.

  1. A superclass may have protected members you want to get access to.
  2. It may implement some methods of an interface that you want your class to implement – now, you have fewer methods to be bothered about.
  3. It may implement an interface's only method in a way that covers some generic aspects of it and then delegates to its own abstract method that is supposed to do a smaller, easier, implementation-specific part.

Is such "convenience inheritance" an anti-pattern?

Here's argument against that.

Since you can't "hide" your class's supertypes from your class's clients, its entire type hierarchy is part of its API, including the "base" class. Clients can store your implementation in a variable of the abstract "base" type. Good luck changing your supertype later.

Since the "base" class does not define semantics of the class and is only extended to get access to certain implementation decisions, it's an implementation detail.

Hence, it makes implementation details part of your class's API which is strictly forbidden in software design.

True, you can make the "base" class package-private, but I'm personally not a big fan of package-private classes since they restrict my ability to change the package hierarchy.

If it is an anti-pattern, should you make your class contain the base class ("prefer composition over inheritance")? Won't it be confusing?

Despite my criticism of the "base" classes, they are convenient, pretty common, especially in older APIs (e.g. Swing) and in practice cause little harm.

Here's an example. There's a BaseDao class in our codebase. Specific DAOs extend from it, inheriting protected convenience methods, for example findWithAppSql().

Note. I mentioned appSql strings in one of my earlier questions. Basically, it's a named SQL statement. They are meant to somewhat decouple clients from the DB: the clients first fetch a statement string by its name, put parameter values if necessary, and then actually hit the DB with that statement. For example, there could be an appSql string called SELECT_DOCTORS which contains a query like SELECT * FROM doctor d WHERE (:ID IS NULL or d.id = :ID). You can say appSql strings are the DB's API, similar to how HTTP endpoints are the API of a web server. We don't have a separate backend layer. Instead, the DB in effect doubles as the backend.

public class BaseDao {

    // ...

    protected <T> List<T> findWithAppSql(String appSql, Class<T> entityType, QueryData params) {
public class DoctorDao extends BaseDao {

    public List<DOCTOR> findDoctors() {
        return findWithAppSql("SELECT_DOCTORS", DOCTOR.class, QueryData.empty());
    }

If you make the BaseDao methods public and make it a field instead of supertype of "specific" DAOs, a few things happen.

  1. It becomes so tempting to inject the BaseDao directly, especially if clients need to load multiple types of entities. Why do you need to inject, like, three or four DAOs if you can inject BaseDao and load any entities you want? Surely, it would make an inferior design, but I can easily imagine that happening. Keep in mind, we don't use ORM, so if you need to fetch, say, a doctor's department that they reference with a foreign key, you can't just call doctor.getDepartment() and expect it to return a referenced entity. Instead, you would call doctor.getDepartmentId() and fetch the department yourself (probably, with a DepartmentDao).
  2. It becomes semantically confusing. Why is it a field if it's called a base DAO? Giving it a new name can clear things up (for example, ReinventedEntityManager).
25
  • 1
    "Since the "base" class does not define semantics of the class and is only extended to get access to certain implementation decisions, it's an implementation detail." What makes you say that? Inheritance for code reuse only is why its often criticised, but that is not the only way to use that mighty tool. Commented Sep 18 at 19:40
  • 2
    Also, i dont think the software police actually strictly forbids anything, but merely gives advice and guidelines. Commented Sep 18 at 19:42
  • 2
    "However, Doc Brown forced my hand." - well, I was not the first one who asked for an example, and I was not the only one who voted to close this question as unclear. And I have still trouble with this question. "extending an abstract "base" class" - what else would you do with an abstract class if not extend it? Are you asking whether we should only extend non-abstract base classes? Or are you asking whether inheritance is an anti-pattern in general? Or are you talking about using inheritance to have a common place for some reusable methods? Commented Sep 20 at 13:36
  • 2
    ... so what you are really asking is not whether extending an abstract base class is an antipattern, but extending a base class (abstract seems to be irrelevant) for the sole purpose of sharing some reusable methods? Please clarify. Commented Sep 21 at 8:38
  • 2
    @SergeyZolotarev: I don't know what's wrong with your example, you were the one who wrote "That's why I didn't want to add an example", giving me the impression I misunderstood here something. Commented Sep 22 at 9:30

3 Answers 3

5

IMHO implementation inheritance is not an anti-pattern in general.

There are too many examples where this works well enough to ban this kind of inheritance dogmatically. You presented a few examples where the convenience wins over other aspects, and I could extend the list by several examples by myself, hence it should be clear that we talk about a trade-off, not a no-go.

So under which circumstances is this kind of inheritance ok, and when not?

I think the most important aspect here is to stick to a single dimension for a single inheritance tree.

What does that mean? It means, for example, not to mix a domain model dimension with technical dimensions.

For example, let's take your BaseDAO. Using inheritance for domain concepts, you can have classes like PersonDAO, DoctorDAO, or PrescriptionDAO, modeling the domain dimension.

But now lets assume you want to support different database systems, and you want special DAO classes for PostgreSQL, Oracle and maybe MongoDB. Then you create PostgreSQLDAO, OracleDAO and MongoDBDAO, using inheritance for a technical dimension. This, however, mixes up badly with domain inheritance, one would need to combine each of the domain classes with each of the DBMS specific classes (like a PersonPostgreSQLDAO etc. ). Even with two dimensions, this would lead to an explosion of classes, let alone the fact it now becomes unclear whether PersonPostgreSQLDAO should inherit from PersonDAO, from PostgreSQLDAO or just from BaseDAO.

So how do we solve this issue? We can, for example, stick with the domain dimension for the DAOs, model the DBMS hierarchy separately (for example, with a base class DBMS and subclasses PostgreSQLDBMS, OracleDBMS and MongoDBMS), and then let BaseDAO hold a reference to a DBMS instance. A BaseDAO method like findWithAppSql may then delegate any DBMS specific work to the specific DBMS instance. Or, you can do this the other way round: DBMS may have generic methods which work on BaseDAO objects, or each DBMS entry may provide a list of loaded (cached) DAO objects.

So this all boils down to use inheritance for one dimension only per tree. Composition is the right tool when you need a second dimension which should behave orthogonal to the first one. As long as the design sticks to this guideline, implementation inheritance will not cause too much trouble.

Finally, I would like to mention that IMHO a class like BaseDAO (or simply DAO, as I would call it), has semantics. It classifies all subclasses as Data Access Objects of your system, which gives them a clear purpose and distinguishes them from other classes. Maybe you have other classes in mind, where the semantics isn't so clear as here - this, however, may be simply a question of finding a better name for the common base class, or making slight adjustments to those classes.

3
  • Sounds a lot like the bridge design pattern Commented Oct 7 at 15:45
  • I agreed with "base classes have semantics". How ever, that is the premise and not an addon to this answer, since you cant "Stick to a dimension" without semantics. Maybe open with that? Commented Oct 7 at 15:50
  • 1
    @sfiss: I see your point. Still I think these are probably two sides of the same coin. Whether sticking to a single dimension is a premise of the base class having a clearer semantics, or if it is the other way round may be simply different point of views. I think it is more important to understand that a "technical dimension" can lead to semantics as well as a domain dimension. Both can be viable options for modeling, as long as one does not mix them together. Commented Oct 7 at 17:10
2

Is extending an abstract "base" implementation class an anti-pattern?

Understand that "anti-patterns" are context dependent.

Chopsticks are an anti-pattern, in the context of pizza. Not in the context of ramen.

So what context makes extending an abstract "base" implementation class an anti-pattern? Ownership.

public class MyLoginPageWasNull extends NullPointerException{}

I don't consider this an antipattern because I don't own NullPointerException. This is nice way to give an exception a more descriptive name that works in my domain.

If I did own it, and NullPointerException inherited from something else that I also owned then you'd be right to complain, because this is what causes the yo yo problem.

An ownership boundary creates behavior that can't be moved. That's when I consider dealing with multiple inherited behavior layers an annoying necessity. Doing this to yourself within your own code base is unnecessary pain. Just because you saw people do this with library code doesn't mean that's what you should do with your code. They are dealing with different problems than you are. Stop imitating them.

But, if you are creating a library, and want to be nice to the rest of us, consider this.

13
  • What a weird heuristic. I tend to disagree: 1) inheriting from something you dont own while violating LSP is wrong. 2) inherting, even as a hierarchy, from something you own may cause other problems but can be a non-anti-pattern in general (i remember some gang writing about patterns that heavily use inheritance for polymorphism). Commented Sep 19 at 4:45
  • @sfiss I remember that gang. I also remember them putting themselves on trial for their crimes against software engineering. Commented Sep 19 at 4:59
  • Hmm, c2 has link rot. Try this. Commented Sep 19 at 5:10
  • 1
    @sfliss you're right. Inheriting an abstract class works just as well. That's the problem. Yes, polymorphism comes in many flavors. But LSP and semantics wont save you from the yo-yo problem you just created. It isn't about it working well. It's about how miserable you're making those that will maintain it. Commented Sep 19 at 7:17
  • 1
    You may not own it, but there is a relationship nonetheless: If you can credibly say that MyLoginPageWasNullException IS A NullPointerException (albeit a specialized one), then you're good to go. Ownership (or the lack thereof) doesn't confer any special meaning here; you can still write the base class, inherit from it, and own all of it. The same relative merits (or demerits) would still apply. Commented Sep 19 at 17:00
0

I don't quite understand your problem but going by the one example you provide you may think of it differently and the following may apply in more cases.

Circle does not have an "is a" relationship with ellipse although it may seem so. Inheritance is to extend, not to restrict. Circle is nothing more than an arbitrary (boolean) property of ellipse. It's an ellipse with two particular properties having the same value.

So whenever you feel something is wrong with inheritence, the alternative may not be encapsulation or composition, your whole understanding of the world may be wrong. It may not be a class you are looking at or for.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.