2

I'm a relative newcomer to OOP, and I'm having a bit of trouble creating good designs when it comes to interfaces.

Consider a class A with N public methods. There are a number of other classes, B, C, ..., each of which interacts with A in a different way, that is, accesses some subset (<= N) of A's methods.

The maximum degree of encapsulation is achieved by implementing an interface of A for each other class, i.e. AInterfaceForB, AInterfaceForC, etc.

However, if B, C, ... etc. also interact with A and with each other, then there will be a combinatorial explosion of interfaces (a maximum of n(n-1), to be precise), and the benefit of encapsulation becomes outweighed by a code-bloat.

What is the best practice in this scenario?

Is the whole idea of restricting access to a class's public functions in different ways for other different classes just silly altogether? One could imagine a language that explicitly allows for this sort of encapsulation (e.g. instead of declaring a function public, one could specify exactly which classes it is visible to); Since this is not a feature of C++, maybe it's misguided to try to do it through the back door with interaces?

  • 2
  • It might also be good to know the domain. Certain domains, such as game design, are sometimes more amenable to other paradigms such as component or entity based architectures. When I have run into the types of problems you're facing now in my own programs I have usually found that I either have a poor design due to a not-so-great decomposition of parts or a different paradigm is a better fit. – J Trana Jun 13 '14 at 0:34
  • You said “One could imagine a language that explicitly allows for this sort of encapsulation (eg instead of declaring a function public, one could specify exactly which classes it is visible to)” — an example is Eiffel, but don't over use this feature. Also look at design by contract, this may help you group features/methods into interfaces. One interface per client is too many. Usually you will have one interface, add interfaces when it has many roles. Eg a bus is driven (much like any large vehicle), but it also carry people, therefore 2 interfaces. – ctrl-alt-delor Jun 13 '14 at 9:38
  • 1
    book recommendation: touch.ethz.ch – ctrl-alt-delor Jun 13 '14 at 9:49
  • "(e.g. instead of declaring a function public, one could specify exactly which classes it is visible to)" This is awful, because it runs completely counter to modular design. Good code is easy to glue together with other code to form a new whole. That aside, if you rely on immutability you don't have to be paranoid about who has access to an object. Don't count on being able to do that in C++ though, since that sort of pervasive immutability is only feasible with garbage collection. You could use reference counting, but you get worse performance and have to be careful of cycles. – Doval Jun 13 '14 at 12:12
7

The point of interfaces is to allow piece of code to express what API it works with. And then allow this API to be implemented by multiple classes. In your case, instead of AforB or AforC, make it Bneeds and Cneeds interface.

What you are saying also hints that there is actually only one implementation of those interfaces in form of A. If there is only one implementation, then there is serious flaw in the design, because point of interface is to be able to be implemented by multiple classes. If interface limits by which class it is implemented, then it is bad interface.

  • So you're saying that an interface should be implemented by multiple classes, not the other way round. However, the Interface Segregation Principle (linked to by @RobertHarvey) seems to suggest otherwise (see the Xerox example): a number of interfaces that a single class (and only that class) implements. Maybe this symptomatic of a bad design in the first place? – MGA Jun 12 '14 at 21:31
  • @mga ISP doesn't suggest anything like that. I would suggest not taking ISP literary, because it is quite vague to use as a rule. There is nothing that limits number of interfaces that can be implemented by a class. Other than SRP, but which is different matter entirely. – Euphoric Jun 12 '14 at 21:45
  • Interfaces don't need multiple implementation, but If they don't then you don't have polymorphism. – ctrl-alt-delor Jun 13 '14 at 9:46
  • ISP states make an interface per role: A pipe can be written to and read from, so 2 interfaces, then maybe you add latter a management interface. But you don't have one per client, if you do you have too much dependency. You have to edit the class every time you add a client. – ctrl-alt-delor Jun 13 '14 at 9:47
  • @richard: A lot depends upon what the interfaces are for. Some common real-world situations require double dispatch for optimal implementation (e.g. render data which may be in one of many forms into one of many file formats). Even if it would be possible to define an intermediate format to which all data could be converted, and which could then be translated into every possible file format, there would likely be some frequently-used combinations of input and output formats where such an approach would be horribly inefficient. – supercat Oct 8 '14 at 16:51
6

Is the whole idea of restricting access to a class's public functions in different ways for other different classes just silly altogether?

Yes and no. Bear with me.

Restricting access to a specific class is silly. While it is important to separate a class from the implementation of its dependencies, it's arguably more important to separate a class from the details of the class dependant on it.

For this reason, it's also silly to name your interfaces as if they are a link between the two classes. They're not.

To the calling class, as Euphoric points out, the interface is "I need to be able to do this ... I don't care how it's done." The name should reflect that. ie. Rather than an interface between ProductService and SqlProductRepository called SqlProductRepositoryForProductService, there should be an interface known to the ProductService called ProductRepository ("I need to be able to save a product and I don't care how"), which is implemented by SqlRepository.

But SqlRepository may also be able to store Orders. And in this sense, it DOES make sense to limit access FROM ProductService to SqlRepository so that it can only store Products. OrderService may access the SqlRepository through a new interface called OrderRepository.

Why bother, you ask? Why not simply access the class, or an interface that gives you access to the whole class?

Because it might be that, once SqlRepository has grown beyond control, you want to break it down. Perhaps into a SqlMembershipRepository (which might handle users, groups, roles, etc) and a SqlManufacturingRepository (which might handle products, components, etc) and a SqlSalesRepository (which might handle orders, customers, etc). If you've allowed everything access to all of the SqlRepository, you might find this a much tougher task than simply breaking the class into three and implementing the same interfaces that the Services always needed. Especially true if you have several classes that need to save a product.

Or, and this may be the most important point, you might want to change the way you handle Sales only. For some reason, you might decide to store it in a file, or in a NoSql database.

If you've kept your interfaces segregated, you only need to implement the ProductRepository interface on the new NoSqlRepository. If you kept it as one interface, you would have to implement SaveOrder in the NoSqlRepository (even if it were a blank method, because it was never used) and leave the implementation of SaveProduct on SqlRepository.

Suddenly your entire abstraction between services and repositories would make no sense. Another developer may come along and write some code that will store an order. There is a very good chance that they'll accidentally call the empty SaveOrder method on the NoSqlRepository, instead of the SaveOrder method on the SqlRepository. And they may be very confused when that doesn't work.

  • 1
    @pdf Very good explanation, thank you. In hindsight, I think I hyperbolized my example a little, and as I am a beginner in OOP, I would be better off looking at a simpler, more concrete case. If you don't mind a follow-up question, consider this scenario: A has 2 public methods: Foo() and Bar(). B and C both act on A, but B requires both methods, while C requires only Foo(). In that case, would you implement an interface to hide Bar() from C? – MGA Jun 13 '14 at 0:05
  • 1
    @mga: Not knowing what Foo and Bar do, it's hard to say. I can think of circumstances where I'd give either answer. And circumstances where I'd derive one interface from the other, and circumstances where I wouldn't. Unfortunately, this is the kind of thing where experience will tell you what to do. Whether that be an experienced colleague, or an answer to a specific question here or on codereview, or simply taking an educated guess and learning from getting it wrong. (Which is what most of us have done at some point.) – pdr Jun 13 '14 at 0:14
1

If B and C need different things, there's probably a need for two interfaces. If those happen to be implemented by the same class, so be it.

What strikes me here is your inter-dependency of the classes. In order to be able to refactor code at a later point your objects shouldn't depend on each other. Even if you hide it with interfaces, they shouldn't be calling each other, which seems to me to be the actual flaw here. Maybe what you actually need is some kind of facade but that's a wild guess without knowing the details.

I think you should rethink A using B and, at the same time, B using A. That's probably the root cause of your problem.

  • Good point on the design being flawed if all classes are using each other. In an attempt to get my point across, I overstated it. Please see the comment I put under pdr's post, with a much simpler, more concrete scenario. – MGA Jun 13 '14 at 0:08
  • @mga For what it's worth, I agree with pdr, especially with the last comment: experience will probably answer you when to split interfaces. If you want to solve the particular case here, try asking a more concrete question, as suggested. :) – jhr Jun 13 '14 at 5:00
0

This would indicate to me that a class is too large and/or in-cohesive (https://en.wikipedia.org/wiki/Cohesion_%28computer_science%29). Otherwise you should be guided to the interface by the purpose of the class. Are you trying to do too much in a single class?

Express as small an amount of functionality as possible via the actual public interface of the class but keep it flexible. For example: Say you have a data storage class that can process SQL. Do not put application specific "helper" methods in there. Just use a generic get, put, update, delete methods which take SQL queries. The rest (e.g. std::vector<Record> getDepartmentRecords(const DeptId& dept)) can be handled in wrapping classes.

The last thing is that you should not try to design explicitly for a client of your class, just put the operations on the class that most make sense for it. If some other class wants to restrict their dependencies on your class then it is up to them to decide if they want to use an intermediary interface. Personally I think this is good practise when using third party libraries but for internal dependencies it shouldn't really be necessary (beyond the normal class hierarchy interfaces of course!)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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