📜 ⬆️ ⬇️

Incorrect use of the Bridge / Bridge design pattern

Prehistory

I read this article about the Bridge design pattern. Alas, it is very often used incorrectly. Moreover, I then opened the book Object-Oriented Design. Design patterns . It turned out - and there the authors very vaguely declare the reasons for its presence and when to use it. Therefore, below I will tell you how and why such a use.

Update
')
This is a superficial article that you can not quite accurately interpret. But its dignity is that it is short and introduces into the problematic. For specialists, it may cause questions of deeper content, and for young developers, some misunderstandings, because I argue as a matter of fact with the “Gang of Four”, but I fully agree with Fowler and his approach to refactoring (and indeed they have contradictions between themselves) - but of course who am I to argue.

I am preparing an extended article for specialists, but it may also be useful for young developers to learn the design patterns mechanically. They do not understand very well when to use them, but I think the experts will be important arguments in the sense that of the patterns preferred. This extended article will hopefully explain why it is necessary to get rid of the “Bridge” pattern, and also to use the Mediator pattern in a limited sense.

There is already an offshoot of this article. Proper use of the Bridge pattern (Two-way bridge) or MVC -> “Business entity - Visualization - Controller” . Where it is shown that the Bridge / Mediator can be used in some combination in the separation of visualization and business logic, but this is practically the only area for these templates. In pure business logic and low-level / system tasks, these patterns should be avoided.

But I can not publish - there is no karma, but as I understand there are no other options, until I type. So you want to read, you know what to do :)

What is the “Bridge” design pattern in reality?

If you know object-oriented programming, then I declare with all responsibility that knowing about patterns is not necessary at all. Patterns are only a particular and not always the most successful solution based on OOP principles.

Let's see what the “Bridge” pattern is, what lies behind this abstruse term. This is nothing more than a combination of applying inheritance and aggregation. Alas, they often do not know what aggregation is. Simply, this is when one object is included in another.


The true reasons for using the Bridge pattern

The authors of the book declare such a goal: “to separate the abstraction from its realization”.

But this is completely wrong. As a principle in general, it is even harmful. This is contrary to the PLO. The implementation must fully comply with what is declared in the abstraction. Otherwise, the price is generally worth separating the declaration from the sale. If we look at the class declaration, and then it turns out that the implementation of the class does not match it, then we have lost the most important thing - we have lost polymorphism . We then become forced to familiarize ourselves with the implementation, and it is not enough for us to know only the abstraction / specification.

But the matter turns out to be somewhat different. And it becomes clear through examples. Shows examples where inheritance is used. But inheritance is not simple, but according to two different criteria. For example, “Windows” can be classified for different operating systems: XWindow, PMWindow, ..., and different in purpose: windows for icons (IconWindow), windows for messages (MessageWindow) ...

Then the final window should contain functions from one hierarchy and functions from another. Those. to be specialized for the purpose and for the operating system. Then, of course, the inheritance apparatus becomes problematic, it turns out that it is necessary to create classes as a multiplication of these hierarchies, i.e. XIconWindow, PMIconWindow, XMessageWindow, PMMessageWindow. Of course, this already violates another principle of OOP - the creation of redundant entities, which are just a combination of simpler ones.

Note that this is a classic example of applying multiple inheritance. Those. XIconWindow is an inheritor from IconWindow and XWindow. But the latest trends have identified multiple inheritance as inappropriate OOP. We will not discuss here, but really theoretically multiple inheritance is superfluous, and there is more likely a misunderstanding of the subject area.

So, the authors of the pattern offer a different way. They saw the particular that one of the hierarchies is responsible for the physical implementation, i.e. for what OS it is done. And, of course, it must be hidden. But this is only a particular, but in fact there may be other hierarchies. For example, windows may differ according to the Single Window (SDI) or Multiwindow (MDI) principle. And what, another hierarchy multiplication?

And of course not. It is saved here by the understanding that these hierarchies of objects are their properties. And how to implement the properties? That's right, including them in the designated object. Those. apply aggregation.

This is what makes the “Bridge” pattern. But, in essence, this pattern is a clumsy explanation of the more general principles of OOP.

And how then to argue more correctly?

Very simple. There is a “general-private” relationship - we use inheritance, there is a “part-whole” relationship, we use aggregation. But sometimes this is not so obvious, and it seems to us that there is a “general-particular” relationship, but according to two, three different criteria (as described above). But we remember that such reasoning will only allow us to get confused, and accordingly, worsen the architecture of the program. Then we must decide which criteria are more important and which are less. Build them into a hierarchy, put one in the other. The authors of the “Bridge” pattern consider that the implementation of the feature to the operating system is less important than the purpose of the window. But this is only a special case. There may be other criteria, and each time it is necessary to decide what is a part and what is a whole.

Moreover, this will often have to be solved when a part is already implemented, and an additional statement has appeared. Then you have to do refactoring and turn inheritance into aggregation. Therefore, we must remember that inheritance creates a more rigid connection between classes. And always, when there is an opportunity, it is better to prefer aggregation. This will allow to more clearly form the specification of the class and the relationship between different classes. Inheritance should be used very carefully. Otherwise, then you have to redo it into an aggregation - and this is often not so simple. Inheritance should be used only when you cannot clearly identify the criteria for hereditary classification, i.e. when you know that this class is a subspecies of a more abstract class, and not by some criterion, but in general.

But never single out the implementation of the “alarm clock” from the abstraction of the “alarm clock”. It speaks only about the lack of understanding of the PLO. So in the article, which served as the reason for writing this note, as an “bridge”, the entity announced as the implementation of the alarm clock was introduced. Interesting, but what remains in the so-called. alarm clock abstraction - not implementation?

In fact, there are two functions there - call and display a message. And it can be done in different ways. Obviously, the combination of these functions is artificial, since their different combinations are possible. Therefore, the abstraction of the alarm clock in us consists / uses not a pseudo-realization, but two entities — the abstraction of the message manager and the melody player manager. And already specific classes with the help of designers set what type of messages and how to play the tunes you need.

There is only one conclusion: design patterns, especially poorly thought out ones such as the “Bridge” discussed, and especially those who do not understand the more important principles, only hinder, confuse programmers, and their programs.

upd. Additional explanations.

The article is calculated that the reader is familiar with what a “Bridge” is. I beg you not to confuse the “Bridge” pattern with:
1. Simple aggregation - when engines are aggregated into a machine (regardless of how many types of machines and engines there are)
2. Using interfaces, it looks like the first, with the only difference that instead of an abstract class, an interface is used, such as I, Engine, IM

All this does not reach the need for the “Bridge” pattern (too small, that is, the “Bridge” pattern is used according to the intention of the authors in more complex cases). Once again, it is declared that when using “Bridge”, ALL implementation is allocated, i.e. ALL low-level functions to another class, i.e. Not the functionality of the Engine from the Machine, but the low-level functionality (private) of the machine itself in the Machine Realization.

If you understand by “Most” something else, then this article is not for you. You are doing everything right, or just have not yet encountered a situation when you need a “Bridge” - and very well, because it is not really needed.

Another “Bridge” - can be implemented using multiple inheritance, so check yourself and your examples. For example, the first two types - “simple aggregation” and “use of interfaces” - obviously show that there is no place to insert multiple inheritance - it means that “Bridge” will have nothing to do here.

Or it may even be easier - would it occur to you to inherit the Engine from the Machine? Or let's say the sender's letter of the letter? Or an abstraction of a database or a class for working with files from Windows?

But in the examples of using the “Bridge” pattern, this is the initial position, which must be corrected using the “Bridge”. But think about whether such types of inheritance are possible? Why do you think that you used the “Bridge” when even your initial situation was different and about something else?

The story of the "bridge" begins when there is inheritance, and not in one section. Those. there is little that is inherited from the car passenger car, cargo machine. It is necessary in the system to still wish to inherit from the Machine - Machine For Tourism, Machine For Transportation ... And then want to have the combination classes Passenger Machine For Tourism, Passenger Machine For Transportation ... and only when such horror appears - you will have to use the "Bridge" (otherwise your situation is not that, and you even if you wish, do not apply the bridge). But I really hope not as the authors of the pattern describe, but as this article explains.

upd2. Let's try to remove another misunderstanding.

Of course, to implement the “Bridge”, aggregation of abstract classes is used, or interfaces are used, or something else. It's not about that. It is about the reasons - when to use the “Bridge” pattern, and when not. It's about motivation to use. When there is a certain motivation described by the authors, and partly here in the article - that’s necessary. But this does not mean the opposite, that everywhere where you used interfaces or abstract classes - this will be a bridge. So the examples given, with the Machine and the engine will not be the “Bridge” pattern, purely semantically, although syntactically you can see the same interfaces - but this will not be the “Bridge”.
This is just one aspect. The other one and the main one in the article, when you really came up against the situation in which the authors recommend using the “Bridge”, I do not recommend it - this is harmful, and there are other ways. But the main thing I do not recommend is to argue as the authors of the pattern - select the implementation of the abstraction.

upd3. I have some problems with comments. Here I answer two questions.

1. Regarding multiple inheritance for TheShock. Well, apparently because the compiler cannot verify whether you did it or not, and therefore, errors are possible.

2. An interesting problem from Colwin. One of the options. The first step is creating a hierarchy, OS, with the heirs of Win and Os2. Creating ImageProcessing interface with ImageMap LoadFromFile () and Paint (BigImage argImage) methods. Win and Os2 implement ImageProcessing, each in its own way. I get rid of BigImageImp by refactoring into the prepared hierarchy. In BigImage I aggregate the interface ImageProcessing im. In the heirs of BigImage, the Load method first calls im.LoadFromFile (), and Show finally calls im.Paint (this).

Can you say that the bridge too? But then, at least, the semantics became much clearer.

Option two (made from the first, and you can even in parallel, that is, leaving the first option robotic). You can make the client use through the OS, i.e. by calling OS.Paint (argBigImage). To do this, on the contrary, the operating system calls the BigImage methods. Those. Paint calls argImageMap = LoadFromFile (), then argBigImage.Load (argImageMap) - the heir to the image, for example, BmpBigImage does its specifics. Then, the OS calls argBigImage.PrepareForShow () (this clarifies the Show method's meaning in the image, because in fact it turns out that it cannot display itself), and then the OS actually displays. And then there is definitely no bridge. And if you remove the first solution, you can get rid of the ImageProcessing interface, but this is optional. And by the way, we note that the second option can be thought up only by expanding the first one on the correct semantics, and thinking within the framework of the bridge doesn’t really have any options, and the solution is bad.

Source: https://habr.com/ru/post/138357/


All Articles