2.3 Structural Patterns

Lecture



Structural patterns address the question of how larger structures are formed from classes and objects. Structural patterns at the class level use inheritance to compose compositions from interfaces and implementations.

2.3.1 Adapter Pattern

Title

Adapter (adapter), Wrapper (wrapper)

Task

Sometimes a class from the instrumental library cannot be used only because its interface does not correspond to the application being created. In some cases, we can change the interface to the desired one, but if we do not have the source code of the library, this solution is not suitable. Another option is to define another class in such a way that it will adapt the interface of one class to another interface.

The Adapter pattern converts the interface of one class to the interface of another that customers expect. The adapter ensures that classes work with incompatible interfaces, which would be impossible without it.

results

The first version of the pattern is called the object adapter, the second class adapter. The results of applying object and class adapters are slightly different.

Class adapter

  • Adaptes Adaptee to Target, delegating actions to a specific Adaptee class. Since the structure of the program remains rigid when inheriting, this pattern will not work if we want to adapt the class and its subclasses at the same time.
  • Since the Adapter is a subclass of Adaptee, we can easily replace some of the Adaptee operations.
  • Only one new object is entered. To get to the class to be adapted, no further reference to the pointer is necessary.

Object Adapter

  • Allows one adapter to work with many adaptable objects (and the Adaptee itself and all its subclasses).
  • Makes it difficult to replace Adaptee operations (since the Adapter is not a subclass of Adaptee, we can use polymorphism only by spawning a subclass from Adaptee, replacing the Adaptee operations in it and forcing the Adapter to refer to this class)

Structure

 

  2.3 Structural Patterns

Figure 2.8 Structure of the Adapter pattern (object adapter)

  2.3 Structural Patterns

Figure 2.9 Adapter Pattern Structure (Class Adapter)

2.3.2 Composite Pattern

Title

Composite (linker)

Motivation

Applications such as graphic editors and electrical editors (diagrams allow users to build complex diagrams of simpler components.

  2.3 Structural Patterns

Figure 2.10 Composite pattern structure (example)

The designer can group smaller components to form larger ones, which, in turn, can be the basis for creating even larger ones. In a simple implementation, it would be acceptable to define classes of graphic primitives, such as text and lines, as well as classes that act as containers for these primitives. But such a solution has a significant drawback. The program in which these classes are used must deal differently with primitives and containers, although the user most often works with them uniformly. The need to distinguish between these objects complicates the application. The linker pattern describes how a recursive composition can be applied in such a way that the client does not have to distinguish between simple and composite objects. An example of the application of the template can be seen in Fig. 2.10.

The key to the linker pattern is an abstract class that represents both primitives and containers at the same time. In a graphics system, this class may be called Graphic. It declares operations specific. For each type of graphic object (such as Draw) and common to all composite objects, for example, operations for accessing and managing children. Subclasses Line, Rectangle and Text (see diagram above) define primitive graphic objects. In them, the operation Draw is implemented respectively for drawing straight lines, rectangles and text. Since primitive objects have no descendants, none of these subclasses implements operations related to managing descendants. The Picture class defines an aggregate consisting of Graphic objects. The operation Draw implemented in it calls the same function for each Descendant, and operations for working with descendants are no longer empty. Since the interface of the Picture Class corresponds to the Graphic interface, the Picture object may include other similar objects. The diagram below shows the typical structure of a composite object recursively composed of objects of the Graphic class.

Thus, it is possible to formulate the task for which the pattern is intended. The linker assembles objects into tree structures to represent a part-whole hierarchy. Allows customers to consistently treat individual and composite objects.

 

Structure

  2.3 Structural Patterns

Figure 2.11 Composite Pattern Structure

results

  • defines class hierarchies consisting of primitive and composite objects. From primitive objects one can compose more complex ones, which, in turn, participate in more complex compositions and so on. Any client waiting for a primitive object can work with the composite;
  • simplifies client architecture. Clients can work consistently with individual and objects and with composite structures. Usually, the client does not know whether he interacts with the leaf or composite object. This simplifies client code, since there is no need to write functions that branch out depending on which class object they work with;
  • facilitates the addition of new types of components. New subclasses of Composite or Leaf classes will automatically work with already existing structures and client code. There is no need to change the client when adding new components;
  • contributes to the creation of a common design. However, such ease of adding new components has its downsides; it becomes difficult to impose restrictions on what objects can be included in the composition. Sometimes it is desirable that a composite object can include only certain kinds of components. The linker pattern does not allow static types to be used to implement such constraints. Instead, checks should be performed at run time.

2.3.3 Pattern Decorator

Title

Decorator (decorator)

Tasks

Sometimes it is necessary to impose additional responsibilities on a separate object, and not on the class as a whole. Thus, a library for building graphical user interfaces must be able to add a new property, say, a frame or a new behavior (for example, the ability to scroll to any element of an interface). Add new responsibilities are permissible through inheritance. When inheriting from a class with a frame around each instance of a subclass, a frame will be drawn. However, this solution is static, and therefore not flexible enough. The client can not control the design of the component frame. A more flexible approach is another: place the component in another object, called the decorator, which adds a frame. The decorator follows the interface of the object being decorated, therefore its presence is transparent to the component's clients. The decorator redirects requests to the internal component, but can also perform additional actions (for example, draw a frame) before or after the redirection. Since decorators are transparent, they can invest in each other, thereby adding any number of new responsibilities.

  2.3 Structural Patterns

Figure 2.12 Decorator Pattern Structure (example)

Suppose there is an object of the TextView class that displays text in a window. By default, the TextView does not have scroll bars, as they are not always needed. But if necessary, they can be added using the ScrollDecorator decorator. Suppose we also want to add a bold solid frame around the TextView object. Here can help BorderDecorator decorator. The diagram below shows how the composition of a TextView object with BorderDecorator and ScrollDecorator objects produces an element for entering text, surrounded by a frame and provided with a scroll bar.

The ScrollDecorator and BorderDecorator classes are subclasses of Decorator, an abstract class that represents visual components used to style other visual components. VisualComponent is an abstract class for representing visual objects. It defines an interface for drawing and handling events. Note that the Decorator class simply forwards drawing requests to its component, and its subclasses can extend this operation (see Figure 2.13).

Structure

  2.3 Structural Patterns

Figure 2.13 Structure of the Decorator pattern

results

The decorator pattern has at least two pluses and two minuses:

  • greater flexibility than static inheritance. The decorator pattern allows you to add new responsibilities to an object more flexibly than would be possible in the case of static (multiple) inheritance. The decorator can add and remove responsibilities during program execution. When using inheritance, you need to create a new class for each additional duty (for example, BorderedScrollableTextView, BorderedTextView), which leads to an increase in the number of classes and, consequently, to an increase in the complexity of the system. In addition, the use of multiple decorators to a single component allows arbitrarily combining duties. Decorators make it easy to add the same property twice. For example, to surround a Textview object with a double border, you just need to add two BorderDecorators decorators. Double inheritance to the Border class is at best fraught with errors;
  • allows you to avoid classes overloaded with functions or higher levels of the hierarchy. The decorator allows you to add new duties as needed. Instead of trying to maintain all conceivable possibilities in one complex class that can be customized, you can define a simple class and gradually increase its functionality with the help of decorators. As a result, the application no longer pays for unused functions. It is also easy to define new types of decorators, regardless of the classes that they expand, even if such extensions were not originally planned. When expanding a complex class, it is usually necessary to go into details that are not related to the function being added;
  • the decorator and its component are not identical. The decorator acts as a transparent frame. But the decorated component is not identical to the original one. When using decorators this should be borne in mind;
  • many small objects. When using a decorator pattern in a project, a system often consists of a large number of small objects that are similar to each other and differ only in the way they are interconnected, and not in the class and not in the values ​​of their internal variables. Although the designer who understands the device of such a system can easily configure it, it is very hard to study and debug it.

2.3.4 Facade Pattern

Title

Facade

Purpose

Provides a unified interface instead of a set of interfaces of a certain subsystem. The facade defines a higher level interface that simplifies the use of the subsystem.

Splitting into subsystems facilitates the design of a complex system as a whole. The overall goal of any design is to minimize the dependence of the subsystems on each other and the exchange of information between them. One of the ways to solve this problem is to introduce an object facade, which provides a single simplified interface to more complex system tools.

Structure

  2.3 Structural Patterns

Figure 2.14 Structure of the Facade Pattern

Members

Facade - facade. Knows which classes of the subsystem to address the request; delegates client requests to relevant objects within the subsystem;

Subsystem Classes. Implement the functionality of the subsystem; perform the work assigned by the Facade object; they do not “know” anything about the existence of the facade, that is, they do not store references to it.

Relations

Clients communicate with the subsystem by sending requests to the facade. It redirects them to suitable objects within the subsystem. Although it is the objects of the subsystem that do the main work, the facade may have to convert its Interface to the subsystem interfaces. Clients using the facade do not have direct access to the objects of the subsystem.

results

The facade pattern has the following advantages:

  • isolates customers from components of the subsystem, thereby reducing the number of objects that customers have to deal with, and simplifying work with the subsystem;
  • allows you to weaken the connectivity between the subsystem and its clients. Often the components of the subsystem are strongly connected. Weak connectedness allows you to modify components without affecting customers. Facades help layering the system and structure the dependencies between objects, as well as avoid complex and cyclic dependencies. This can be important if the client and the subsystem are implemented independently. Reducing the number of dependencies at the compilation stage is extremely important in large systems. It would be desirable, of course, that the time it takes to recompile after changing the subsystem classes is minimal. Reducing the number of dependencies due to facades can reduce the number of files that need to be re-compiled after a small modification of some important subsystem. A façade can also simplify the process of moving the system to other platforms, as it decreases the likelihood that as a result of a change in one subsystem, all the others will need to be changed;
  • the facade does not prevent applications from directly accessing subsystem classes, if necessary. Thus, you have the choice between simplicity and generality.

2.3.5 Low Coupling Pattern

Title

Low Coupling

Problem

The degree of coupling (coupling) is a measure that determines how rigidly one element is associated with other elements, or how much data about other elements it possesses. An element with a low degree of connectivity (or weak binding) depends on a not very large number of other elements. The expression "very much" depends on the context, but it is necessary to evaluate it.

A class with a high degree of connectedness (or rigidly connected) depends on many other classes. However, the presence of such classes is undesirable because it leads to the following problems.

  • Changes in related classes lead to local changes in this class.
  • It is difficult to understand each class separately.
  • Complicating reuse is complicated because it requires additional analysis of the classes with which the class is associated.

Decision

Distribute responsibilities in such a way that the degree of connectedness remains low.

results

  • Component changes have little effect on other objects.
  • The principles of operation and the functions of the components can be understood without studying other objects.
  • Convenience reuse.

Example

Let the system have classes Payment, Sale and Register. Suppose you need to create an instance of the Payment class and associate it with the Sale object. Which class should be responsible for performing this operation? Since in the real domain the registration of the Payment object is performed by a Register object, according to the Creator template, the Register object is a good candidate for creating a Payment object. Then an instance of the Register object must pass the message addPayment to the Sale object, specifying a new Payment object as a parameter. The above reasoning is reflected in the fragment of the interaction diagram shown in Fig. 2.15.This distribution of responsibilities implies THAT class   Register has knowledge of   Payment class data (i.e. associated with it) .

Pay attention to the notation adopted in the language UML. The instance of the Payment object is given the explicit name p so that it can be used as a parameter to message 2.

  2.3 Structural Patterns

Figure 2.15 Interaction diagram. Strong connectivity

An alternative way to create a Payment object and associate it with a Sale object is to assign the responsibility for creating a Payment object to the Sale (Fig. 2.16)

  2.3 Structural Patterns

Figure 2.16 Interaction diagram. Weak connectivity

When not to use a pattern

A high degree of binding to resistant elements is not a problem. For example, a J2EE application can be tightly associated with Java libraries (java.util, etc.), since these libraries are widely distributed and stable.

Choose your game

A high degree of binding is not a problem in itself. The problem is tight coupling with unstable in some respects elements.

It is important to understand the following. A developer can provide program flexibility, implement the principle of encapsulation, and adhere to the principle of weak coupling in many aspects of the system. However, without convincing motivation, one should not at all costs fight to reduce the degree of binding of objects. Developers should choose “their game” to reduce binding and provide encapsulation. Special attention should be paid to unstable or rapidly changing elements.

2.3.6 Information Expert Pattern

Title

Information Expert

Problem

What is the most general principle of the distribution of responsibilities between objects in object-oriented design?

В модели системы могут быть определены десятки или сотни программных классов, а в приложении может потребоваться выполнение сотен или тысяч обязанностей. Во время объектно-ориентированного проектирования при формулировке принципов взаимодействия объектов необходимо распределить обязанности между классами.

При правильном выполнении этой задачи система становится гораздо проще для понимания, поддержки и расширения. Кроме того, появляется возможность повторного использования уже разработанных компонентов в последующих приложениях.

Decision

Назначить обязанность информационному эксперту — классу, у которого имеется информация, требуемая для выполнения обязанности.

results

The corresponding behavior of the system is provided by several classes containing the required information. This leads to class definitions that are much easier to understand and maintain. In addition, the High Cohesion template is supported.

Example

In the application, some class needs to know the total amount of the sale. Begin the distribution of responsibilities with their clear wording. From this point of view, we can formulate the following statement.

Which class should be responsible for knowing the total amount of the sale?

According to the Information Expert pattern, you need to determine which classes of objects contain the information necessary to calculate the total amount.

Теперь возникает ключевой вопрос: на основе какой модели нужно анализировать информацию - модели предметной области или проектирования? Модель предметной области иллюстрирует концептуальные классы из предметной области системы, а в модели проектирования показаны программные классы.

Ответ на этот вопрос сводится к следующему:

Если в модели проектирования имеются соответствующие классы, в первую очередь, следует использовать ее.

В противном случае нужно обратиться к модели предметной области и постараться уточнить ее для облегчения создания соответствующих программных классов.

Например, предположим, мы находимся в самом начале этапа проектирования, когда модель проектирования представлена в минимальном объеме. Следовательно, кандидатуру на роль информационного эксперта следует искать в модели предметной области. Вероятно, на эту роль подойдет концептуальный класс Sale. Тогда в модель проектирования нужно добавить соответствующий программный класс под именем Sale и присвоить ему обязанность вычисления общей стоимости, реализуемую с помощью вызова метода getTotal. При таком подходе сокращается разрыв между организацией программных объектов и соответствующих им понятий из предметной области.

Чтобы рассмотреть этот пример подробнее, обратимся к фрагменту модели предметной области, представленному на рис. 2.17.

  2.3 Structural Patterns

Рисунок 2.17 Фрагмент модели предметной области

Какая информация требуется для вычисления общей суммы? Необходимо узнать стоимость всех проданных товаров SalesLineltem и просуммировать эти промежуточные суммы. Такой информацией обладает лишь экземпляр объекта Sale. Следовательно, с точки зрения шаблона Information Expert объект Sale v подходит для выполнения этой обязанности, т.е. является информационным экспертом (information expert).

Как уже упоминалось, подобные вопросы распределения обязанностей зачастую возникают при создании диаграмм взаимодействий. Представьте, что вы приступили к работе, начав создание диаграмм для распределения обязанностей между объектами. Принятые решения иллюстрируются на фрагменте диаграммы Взаимодействий, представленном на рис. 2.18.

Однако на данном этапе выполнена не вся работа. Какая информация требуется для вычисления промежуточной суммы элементов продажи? Необходимы значения атрибутов SalesLineltem.quantity и SalesLineltem.price. Объекту SalesLineltem известно количество товара и известен связанный с ним объект ProductSpecif ication. Следовательно, в соответствии с шаблоном Expert, промежуточную сумму должен вычислять объект SalesLineltem. Другими словами, этот объект является информационным экспертом.

  2.3 Structural Patterns

Рисунок 2.18 Распределение обязанностей в соответствии с паттерном Information Expert

В терминах диаграмм взаимодействий это означает, что объект Sale должен передать сообщения getSubtotal каждому объекту SalesLineltem, а затем просуммировать полученные результаты. Этот процесс проиллюстрирован на рис. 2.19

  2.3 Structural Patterns

Рисунок 2.19 Распределение обязанностей в соответствии с паттерном Information Expert

Для выполнения обязанности, связанной со знанием и предоставлением промежуточной суммы, объекту SalesLineltem должна быть известна стоимость товара. В данном случае в качестве информационного эксперта будет выступать объект ProductSpecification (рис. 2.20).

В завершение можно сказать следующее. Для выполнения обязанности "знать и предоставлять общую сумму продажи трем объектам классов" были следующим образом присвоены три обязанности:

  • Sale - знание общей суммы продажи
  • SalesLineltem - знание промежуточной суммы для данного товара
  • Product Specification - нание цены товара

Рассмотрение и распределение обязанностей выполнялись в процессе создания диаграммы взаимодействий. Затем полученные результаты могут быть реализованы в разделе методов диаграммы классов. При назначении обязанностей, согласно шаблону Expert, был применен следующий принцип: обязанности связываются с тем объектом, который имеет информацию, необходимую для их выполнения.

  2.3 Structural Patterns

Рисунок 2.20 Распределение обязанностей в соответствии с паттерном Information Expert

created: 2014-10-05
updated: 2021-03-13
132649



Rating 9 of 10. count vote: 2
Are you satisfied?:



Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Object oriented programming

Terms: Object oriented programming