0% found this document useful (0 votes)
17 views25 pages

Factory Method - An Overview - ScienceDirect Topics

The document discusses the Factory Method design pattern, which allows object creation without specifying the exact type, addressing limitations of C++ constructors. It explains the use of Abstract Base Classes (ABCs) to define interfaces and presents examples of simple and extensible factories that enable dynamic object creation and registration of new types at runtime. Additionally, it touches on the Open/Closed Principle (OCP), emphasizing the importance of creating stable interfaces that can be extended without modifying existing code.

Uploaded by

PetrosS
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views25 pages

Factory Method - An Overview - ScienceDirect Topics

The document discusses the Factory Method design pattern, which allows object creation without specifying the exact type, addressing limitations of C++ constructors. It explains the use of Abstract Base Classes (ABCs) to define interfaces and presents examples of simple and extensible factories that enable dynamic object creation and registration of new types at runtime. Additionally, it touches on the Open/Closed Principle (OCP), emphasizing the importance of creating stable interfaces that can be extended without modifying existing code.

Uploaded by

PetrosS
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 25

Factory Method

Related terms:

Design Pattern, Application Programming Interface, Constructors, Derived Class,


Refactoring, Subclasses, Closed Principle, Interface Design

Patterns
Martin Reddy, in API Design for C++, 2011

3.3 Factory Methods


A Factory Method is a creational design pattern that allows you to create objects
without having to specify the specific C++ type of the object to create. In essence, a
factory method is a generalization of a constructor. Constructors in C++ have
several limitations, such as the following.
1. No return result. You cannot return a result from a constructor. This means
that you cannot signal an error during the initialization of an object by
returning a NULL pointer, for instance (although you can signal an error by
throwing an exception within a constructor).
2. Constrained naming. A constructor is easily distinguished because it is
constrained to have the same name as the class that it lives in. However, this
also limits its flexibility. For example, you cannot have two constructors that
both take a single integer argument.
3. Statically bound creation. When constructing an object, you must specify the
name of a concrete class that is known at compile time, for example, you might
write: Foo *f = new Foo(), where Foo is a specific type that must be
known by the compiler. There is no concept of dynamic binding at run time for
constructors in C++.
4. No virtual constructors. You cannot declare a virtual constructor in C++. As
just noted, you must specify the exact type of the object to be constructed at
compile time. The compiler therefore allocates the memory for that specific
type and then calls the default constructor for any base classes (unless you
explicitly specify a non-default constructor in the initialization list). It then calls
the constructor for the specific type itself. This is also why you cannot call
virtual methods from the constructor and expect them to call the derived
override (because the derived class hasn't been initialized yet).
In contrast, factory methods circumvent all of these limitations. At a basic level, a
factory method is simply a normal method call that can return an instance of a
class. However, they are often used in combination with inheritance, where a
derived class can override the factory method and return an instance of that
derived class. It's also very common and useful to implement factories using
abstract base classes (ABC) (DeLoura, 2001). I will focus on the abstract base class
case here, so let's recap what these kinds of classes are before I dive further into
using factory methods.
3.3.1 Abstract Base Classes
An ABC is a class that contains one or more pure virtual member functions. Such a
class is not concrete and cannot be instantiated using the new operator. Instead, it
is used as a base class where derived classes provide the implementations of the
pure virtual methods. For example,
// renderer.h
#include <string>
class IRenderer
{
public:
virtual ~IRenderer() {}
virtual bool LoadScene(const std::string
&filename) = 0;
virtual void SetViewportSize(int w, int h) = 0;
virtual void SetCameraPosition(double x, double y,
double z) = 0;
virtual void SetLookAt(double x, double y, double
z) = 0;
virtual void Render() = 0;
};
This defines an abstract base class to describe an extremely simple 3D graphics
renderer. The “= 0” suffix on the methods declares them as pure virtual methods,
meaning that they must be overridden in a derived class for that class to be
concrete. Note that it is not strictly true to say that pure virtual methods provide no
implementation. You can actually provide a default implementation for pure virtual
methods in your .cpp file. For example, you could provide an implementation for
SetViewportSize() in renderer.cpp and then a derived class would be
able to call IRenderer::SetViewportSize(), although it would still have to
explicitly override the method as well.
An abstract base class is therefore useful to describe abstract units of behaviors that
can be shared by multiple classes; it specifies a contract that all concrete derived
classes must conform to. In Java, this is referred to as an interface (with the
constraint that Java interfaces can only have public methods, static variables, and
they cannot define constructors). I have named the aforementioned IRenderer
class with an “I” prefix to indicate that it's an interface class.
Of course, you can also provide methods with implementations in the abstract base
class: not all of the methods have to be pure virtual. In this regard, abstract base
classes can be used to simulate mixins, which can be thought of loosely as
interfaces with implemented methods.
As with any class that has one or more virtual methods, you should always declare
the destructor of an abstract base class to be virtual. The following code illustrates
why this is important.
class IRenderer
{
public:
// no virtual destructor declared
virtual void Render() = 0;
};
class RayTracer : public IRenderer
{
public:
RayTracer();
~RayTracer();
void Render(); // provide implementation for ABC
method
};
int main(int, char **)
{
IRenderer *r = new RayTracer();
// delete calls IRenderer::~IRenderer, not
RayTracer::~RayTracer
delete r;
}
3.3.2 Simple Factory Example
Now that I have reviewed what an abstract base class is, let's use it to provide a
simple factory method. I'll continue with the renderer.h example given earlier
and start by declaring the factory for objects of type IRenderer.
// rendererfactory.h
#include "renderer.h"
#include <string>
class RendererFactory
{
public:
IRenderer *CreateRenderer(const std::string
&type);
};
That's all there is to declaring a factory method: it's just a normal method that can
return an instance of an object. Note that this method cannot return an instance of
the specific type IRenderer because that's an abstract base class and cannot be
instantiated. However, it can return instances of derived classes. Also, you can use
the string argument to CreateRenderer() to specify which derived type you
want to create.
Let's assume that you have implemented three concrete classes derived from
IRenderer: OpenGLRenderer, DirectXRenderer, and MesaRenderer.
Let's further specify that you don't want users of your API to have any knowledge of
the existence of these types: they must be completely hidden behind the API. Based
on these conditions, you can provide an implementation of the factory method as
follows:
// rendererfactory.cpp
#include "rendererfactory.h"
#include "openglrenderer.h"
#include "directxrenderer.h"
#include "mesarenderer.h"
IRenderer *RendererFactory::CreateRenderer(const
std::string &type)
{
if (type == "opengl")
return new OpenGLRenderer();
if (type == "directx")
return new DirectXRenderer();
if (type == "mesa")
return new MesaRenderer();
return NULL;
}
This factory method can therefore return any of the three derived classes of
IRenderer, depending on the type string that the client passes in. This lets users
decide which derived class to create at run time, not compile time as a normal
constructor requires you to do. This is an enormous advantage because it means
that you can create different classes based on user input or on the contents of a
configuration file that is read at run time.
Also, note that the header files for the various concrete derived classes are only
included in the factory's .cpp file. They do not appear in the
rendererfactory.h public header. In effect, these are private header files and
do not need to be distributed with your API. As such, users can never see the
private details of your different renderers, and they can't even see the specific types
used to implement these different renderers. Users only ever specify a renderer via
a string variable (or an enum, if you prefer).
Tip
Use Factory Methods to provide more powerful class construction semantics
and to hide subclass details.

This example demonstrates a perfectly acceptable factory method. However, one


potential drawback is that it contains hardcoded knowledge of the available derived
classes. If you add a new renderer to the system, you have to edit
rendererfactory.cpp. This is not terribly burdensome, and most
importantly it will not affect your public API. However, it does mean that you
cannot add support for new derived classes at run time. More specifically, it means
that your users cannot add new renderers to the system. These concerns are
addressed by presenting an extensible object factory.
3.3.3 Extensible Factory Example
To decouple the concrete derived classes from the factory method and to allow new
derived classes to be added at run time, you can update the factory class to
maintain a map that associates type names to object creation callbacks
(Alexandrescu, 2001). You can then allow new derived classes to be registered and
unregistered using a couple of new method calls. The ability to register new classes
at run time allows this form of the Factory Method pattern to be used to create
extensible plugin interfaces for your API, as detailed in Chapter 12.
One further issue to note is that the factory object must now hold state. As such, it
would be best to enforce that only one factory object is ever created. This is the
reason why most factory objects are also singletons. In the interests of simplicity,
however, I will use static methods and variables in our example here. Putting all of
these points together, here's what our new object factory might look like:
// rendererfactory.h
#include "renderer.h"
#include <string>
#include <map>
class RendererFactory
{
public:
typedef IRenderer *(*CreateCallback)();
static void RegisterRenderer(const std::string
&type, CreateCallback cb);
static void UnregisterRenderer(const std::string
&type);
static IRenderer *CreateRenderer(const std::string
&type);
private:
typedef std::map<std::string, CreateCallback>
CallbackMap;
static CallbackMap mRenderers;
};
For completeness, the associated .cpp file might look like:
#include "rendererfactory.h"
// instantiate the static variable in RendererFactory
RendererFactory::CallbackMap
RendererFactory::mRenderers;
void RendererFactory::RegisterRenderer(const
std::string &type, CreateCallback cb)
{
mRenderers[type] = cb;
}
void RendererFactory::UnregisterRenderer(const
std::string &type)
{
mRenderers.erase(type);
}
IRenderer *RendererFactory::CreateRenderer(const
std::string &type)
{
CallbackMap::iterator it = mRenderers.find(type);
if (it != mRenderers.end())
{
// call the creation callback to construct this
derived type
return (it->second)();
}
return NULL;
}
A user of your API can now register (and unregister) new renderers in your system.
The compiler will ensure that the user's new renderer conforms to your
IRenderer abstract interface, that is, it provides an implementation for all of the
pure virtual methods in IRenderer. To illustrate this, the following code shows
how a user could define their own renderer, register it with the object factory, and
then ask the factory to create an instance of it.
class UserRenderer : public IRenderer
{
public:
~UserRenderer() {}
bool LoadScene(const std::string &filename) {
return true; }
void SetViewportSize(int w, int h) {}
void SetCameraPosition(double x, double y, double
z) {}
void SetLookAt(double x, double y, double z) {}
void Render() { std::cout << "User Render" <<
std::endl; }
static IRenderer *Create() { return new
UserRenderer(); }
};
int main(int, char **)
{
// register a new renderer
RendererFactory::RegisterRenderer("user",
UserRenderer::Create);
// create an instance of our new renderer
IRenderer *r =
RendererFactory::CreateRenderer("user");
r->Render();
delete r;
return 0;
}
One point worth noting here is that I added a Create() function to the
UserRenderer class. This is because the register method of the factory needs to
take a callback that returns an object. This callback doesn't have to be part of the
IRenderer class (it could be a free function, for example). However, adding it to
the IRenderer class is a good idea to keep all of the related functionality in the
same place.
Finally, I note that in the extensible factory example given here, a renderer callback
has to be visible to the RegisterRenderer() function at run time. However,
this doesn't mean that you have to expose the built-in renderers of your API. These
can still be hidden either by registering them within your API initialization routine
or by using a hybrid of the simple factory and the extensible factory, whereby the
factory method first checks the type string against a few built-in names. If none of
those match, it then checks for any names that have been registered by the user.
This hybrid approach has the potentially desirable behavior that users cannot
override your built-in classes.

Read full chapter


URL: https://www.sciencedirect.com/science/article/pii/B9780123850034000038
Design
Martin Reddy, in API Design for C++, 2011

4.6.5 The Open/Closed Principle


Bertrand Meyer introduced the Open/Closed Principle (OCP) to state the goal that
a class should be open for extension but closed for modification (Meyer, 1997).
Essentially this means that the behavior of a class can be modified without
changing its source code. This is a particularly relevant principle for API design
because it focuses on the creation of stable interfaces that can last for the long
term.
The principal idea behind the OCP is that once a class has been completed and
released to users, it should only be modified to fix bugs. However, new features or
changed functionality should be implemented by creating a new class. This is often
achieved by extending the original class, either through inheritance or
composition, although, as covered later in this book, you can also provide a plugin
system to allow users of your API to extend its basic functionality.
As an example of the OCP used to practical effect, the simple factory method
presented in Chapter 3 is not closed to modification or open for extensibility. That's
because adding new types to the system requires changing the factory method
implementation. As a reminder, here's the code for that simple renderer factory
method.
IRenderer *RendererFactory::CreateRenderer(const
std::string &type)
{
if (type == "opengl")
return new OpenGLRenderer();
if (type == "directx")
return new DirectXRenderer();
if (type == "mesa")
return new MesaRenderer();
return NULL;
}
In contrast, the extensible renderer factory that was presented later in Chapter 3
allows for the system to be extended without modifying the factory method. This is
done by allowing clients to register new types with the system at run time. This
second implementation therefore demonstrates the Open/Closed Principle: the
original code does not need to be changed in order to extend its functionality.
However, when adhered to strictly, the OCP can be difficult to achieve in real-world
software projects and even contradicts some of the principles of good API design
that have been advanced here. The constraint to never change the source code of a
class after it is released is often impractical in large-scale complex systems, and the
stipulation that any changes in behavior should trigger the creation of new classes
can cause the original clean and minimal design to be diluted and fractured. In
these cases, the OCP may be considered more of a guiding heuristic rather than a
hard-and-fast rule. Also, while a good API should be as extensible as possible, there
is tension between the OCP and the specific advice in this book that you should
declare member functions to be virtual in a judicious and restrained manner.
Nevertheless, if I restate the OCP to mean that the interface of a class should be
closed to change rather than considering the precise implementation behind that
interface to be immutable, then you have a principle that aligns reasonably well
with the focus of this book. That is, maintenance of a stable interface gives you the
flexibility to change the underlying implementation without unduly affecting your
client's code. Furthermore, the use of extensive regression testing can allow you to
make internal code changes without impacting existing behavior that your users
rely upon. Also, use of an appropriate plugin architecture (see Chapter 12) can
provide your clients with a versatile point of extensibility.

Tip
Your API should be closed to incompatible changes in its interface, but open to
extensibility of its functionality.

Read full chapter


URL: https://www.sciencedirect.com/science/article/pii/B978012385003400004X

Blockchain Technology: Platforms, Tools and Use Cases


Peng Zhang, ... Gunther Lenz, in Advances in Computers, 2018

6.1.3 Consequences of Applying Abstract Factory


Without using a high-level abstraction, such as an abstract factory, creating
constituent accounts for an entity would imply many if–else decisions made at
runtime to determine the appropriate concrete account factory to execute. This
tight coupling is cumbersome since Client (who interacts with the Blockchain
component in the above example) has to be familiar with the implementation
details in order to execute each concrete factory's methods properly. For example,
to create an account structure for either Provider or Insurance entity in this case,
Client must be exposed to both ProviderFactory and InsuranceFactory contracts and
make decisions at runtime regarding which factory methods to call, as shown in
Fig. 6. The example only presents two concrete entity examples, but as the number
of entities scales up or as the departments within an organization scale out, the
Client will have to keep track of an overwhelming amount of detailed
implementation.
Fig. 6. Organizational account creation process without applying the Abstract
Factory pattern.
Reprinted with permission from P. Zhang, J. White, D.C. Schmidt, G. Lenz, Applying software
patterns to address interoperability in Blockchain-based healthcare apps, The 24th Pattern
Languages of Programming Conference, October 22–25, 2017, Vancouver, Canada.

Entity creation with Abstract Factory introduces a loose coupling between the client
(e.g., DApp server) and specific smart contract implementations (i.e., the
Blockchain component). In particular, newly defined entity interactions can inherit
from the abstract contract to preserve common properties and have entity-specific
customizations. Because of the loose coupling, existing contracts remain
unmodified, minimizing contract deprecation. The downside of using this design
in a Blockchain, however, would be the extra storage (and therefore extra cost if
used in a public Blockchain with a mining incentive) overhead incurred by an
added layer of indirection for the abstract factory and its instantiation.

Read full chapter


URL: https://www.sciencedirect.com/science/article/pii/S0065245818300196

T&M Model Architecture


Heinz Züllighoven, in Object-Oriented Construction Handbook, 2005

EXAMPLE
Figure 9.25 shows a class hierarchy for a bank domain that represents the relevant
(banking) values. Examples of domain values include the classes
AccountNumber, Amount, InterestRate, and Date (see Section 8.10).
Domain values are used in almost all frameworks of the business and product
domain layers as well as in the use context layer, as in the case of electronic forms,
modeling bank forms in classes. The fields of these forms are represented by
domain values.

FIGURE 9.25. Example of class hierarchy in a bank domain.

A program fragment of the class LoanApplication, limited to the most


important characteristics, modeling a domain object, LoanApplication, could
look like the code in Figure 9.26.

FIGURE 9.26. Code example of class LoanApplication.

A forms editor used to represent and edit forms on the screen requires appropriate
presentation forms to represent domain values (see Section 8.8). These
presentation forms utilize the specific representation and editing options for
domain values. For example, an amount with decimal places for thousands in a
bank application is separated by commas, and the sign is put after the amount.
Figure 9.25 also shows a class hierarchy, corresponding to the domain values and
presentation forms (PFs). A presentation form, LoanAmountPF, specialized for
the bank's loan business, represents the domain value Amount with a leading sign.
Figure 9.25 shows how the classes are distributed in the T&M model architecture.
For example, the classes LoanAmountPF and LoanApplication are part of
the Loan product domain. All other classes reside in the business domain layer,
because they represent core concepts for the product domains.
The example discussed in this section uses a forms editor for editing signature
forms. This forms editor has to create a matching presentation form for all domain
values it requests by calling the getFields method. We can identify the
following important points for this example:
• The forms editor contains a large case statement to create a suitable
presentation form, depending on the domain value type. Since the editor has
to create the specialized presentation form, LoanAmountPF, for a loan
amount, an invalid dependence to a class (LoanAmountPF) in a higher layer
is produced in the class FormsEditor. In addition, the case statement has to
be adapted for each extension of the domain value hierarchy. Each change to
the class hierarchy of the domain values can cause the business domain to be
opened.
• The domain values implement a so-called factory method (as proposed by
Gamma et al.), which creates an instance of the appropriate presentation form.
The allocation of a domain value to a presentation form cannot then be
dynamically configured. Instead, it is specified once and then used to create the
same presentation form in all applications when the factory method is called.
For example, if we allocate the presentation form AmountPF to the domain
value Amount in the factory method, then we wouldn't be able to adapt this
allocation for a loan application without having to change the code or set
additional parameters. Again, we would have to open the business domain.
• An abstract factory as proposed by Gamma et al., hides the creation of objects
from a client (FormsEditor, in this example), by providing an abstract
operation to create a matching presentation form for each domain value. A
specific factory implements abstract operations by creating the desired
presentation forms. Though this design pattern allows a specific factory for the
loan domain to allocate the domain value Amount to the presentation form
LoanAmountPF, it has a few problems. The FormsEditor has to
implement an expensive case statement to determine which operation of the
abstract factory has to be called, depending on the domain value type. New
domain values lead to new abstract operations in the abstract factory. Changes
to the domain value hierarchy or the presentation forms require us to open the
business domain, even when these changes are implemented in one single
product domain.
• Alternatively to using a factory, the forms editor could be specialized in each
product domain, with specific requirements to the representation and editing
of domain values. Unfortunately, this may cause problems when the forms
editor is required in a use context where specialized value representations from
several product domains are required. In this case, we would have to add
another specialization of the FormsEditor class in this use context. To
implement such a specialization, we would have to use multiple inheritance of
copy code. None of these two implementation methods is elegant.
Read full chapter
URL: https://www.sciencedirect.com/science/article/pii/B9781558606876500098

Extensibility
Martin Reddy, in API Design for C++, 2011

12.2.6 Prohibiting Subclassing


As a final note on extending via inheritance, I will address the situation where you
want to prohibit users from inheriting derived classes from the classes you provide
to them. In Java, you can declare a class to be final to prevent others from
inheriting from it, but C++ does not have a similar concept.
As already noted, if you declare a class with a non-virtual destructor, this should be
a signal to a good programmer that they should think twice about inheriting from
the class. However, if you want a physical mechanism to prevent users from
subclassing one of your classes, the easiest way to do this is to make all of its
constructors private. Any attempt to derive from this class will produce a compile
error because the derived class's constructor cannot call your class's constructor.
You can then provide a factory method to let users create instances of the object.
class NonBase
{
public:
static NonBase* Create() { return new NonBase ();
}
private:
NonBase();
};
class Derived : public NonBase {};
Derived d; // compile error!
The downside of this approach is that instances of NonBase cannot be created on
the stack. Your clients must instead always allocate instances of NonBase using
the NonBase::Create() static function. If this is undesirable, there is another
solution. You can instead rely on virtual inheritance to ensure that no concrete
class can inherit from NonBase, as follows (Cline et al., 1998).
class NonBase;
class NonBaseFinal
{
private:
NonBaseFinal() {}
friend class NonBase;
};
class NonBase :virtual public NonBaseFinal
{
public:

};
class Derived : public NonBase {};
Derived d; // compile error!

Read full chapter


URL: https://www.sciencedirect.com/science/article/pii/B9780123850034000129

Encapsulation Smells
Girish Suryanarayana, ... Tushar Sharma, in Refactoring for Software Design
Smells, 2015

4.3 Missing Encapsulation


This smell occurs when implementation variations are not encapsulated within an
abstraction or hierarchy.
This smell usually manifests in the following forms:
• A client is tightly coupled to the different variations of a service it needs. Thus,
the client is impacted when a new variation must be supported or an existing
variation must be changed.
• Whenever there is an attempt to support a new variation in a hierarchy, there is
an unnecessary “explosion of classes,” which increases the complexity of the
design.
4.3.1 Rationale
The Open Closed Principle (OCP) states that a type should be open for extension
and closed for modification. In other words, it should be possible to change a
type’s behavior by extending it and not by modifying it (see case study below).
When the variations in implementation in a type or a hierarchy are not
encapsulated separately, it leads to the violation of OCP.

CASE STUDY ON OCP


Localization in Java is supported through the use of resource bundles (see
java.util.ResourceBundle in Java 7). We can write code that is mostly
independent of user’s locale and provide locale-specific information in resource
bundles.
The underlying process of searching, resolving, and loading resource bundles is
complex. For most purposes, the default implementation provided by the
ResourceBundle class is sufficient. However, for sophisticated applications,
there may be a need to use a custom resource bundle loading process. For
instance, when there are numerous heavyweight locale-specific objects, you may
want to cache the objects to avoid reloading them. To support your own
resource bundle formats, search strategy, or custom caching mechanism,
ResourceBundle provides an extension point in the form of
ResourceBundle.Control class. Here is the description of this class from
JDK documentation:
“ResourceBundle.Control defines a set of callback methods that are invoked by
the ResourceBundle.getBundle factory methods during the bundle loading
process… Applications can specify ResourceBundle.Control instances returned
by the getControl factory methods or created from a subclass of
ResourceBundle.Control to customize the bundle loading process.”
In summary, ResourceBundle supports a default process for locating and
loading resource bundles that is sufficient for most needs. Furthermore, the
class also provides support for customizing this bundle locating and loading
process by providing a separate abstraction in the form of
ResourceBundle.Control class. Thus, this design by default supports the
standard policy and provides an extension point to plug-in a custom policy.
Such a design follows OCP.
This example is also an illustration of what Alan Kay (Turing award winner and
co-creator of Smalltalk) said about design: “Simple things should be simple,
complex things should be possible.”

One of the enabling techniques for Encapsulation is “hide variations.” This is


similar to the “Variation Encapsulation Principle (VEP)” which is advocated by
Gamma et al. [54]. Thus, when a type (or hierarchy) fails to encapsulate the
variation in the implementation, it implies that the principle of encapsulation has
been either poorly applied or not applied at all. Hence, we term this smell Missing
Encapsulation.
4.3.2 Potential Causes
Lack of awareness of changing concerns
Inexperienced designers are often unaware of principles such as OCP and can end
up creating designs that are not flexible enough to adapt to changing
requirements. They may also lack the experience to observe or foresee the concerns
that could change in the future (based on the planned product roadmap); as a
result, these concerns may not be properly encapsulated in the design.
Lack of refactoring
As existing requirements change or new requirements emerge, the design needs to
evolve holistically to accommodate the change. This is especially crucial when
designers observe or foresee major problems such as explosion of classes. The lack
of refactoring in such cases can lead to this smell.
Mixing up concerns
When varying concerns that are largely orthogonal to each other are not separated
but instead aggregated in a single hierarchy, it can result in an explosion of classes
when the concerns vary.
Naive design decisions
When designers naively use simplistic approaches such as creating a class for every
combination of variations, it can result in unnecessarily complex designs.
4.3.3 Examples
Example 1
Assume that you have an existing Encryption class that needs to encrypt data
using an algorithm. There are various choices for such an algorithm including DES
(Data Encryption Standard), AES (Advanced Encryption Standard), TDES (Triple
Data Encryption Standard), etc. Figure 4.9 illustrates how the Encryption class
encrypts data using the DES algorithm. Suppose a new requirement is introduced
that requires data to be encrypted using the AES algorithm. A novice developer
may come up with the design shown in Figure 4.10, in which he introduces
different methods such as encryptUsingDES() and encryptUsingAES()
in the Encryption class.

FIGURE 4.9. The class Encryption provides DES algorithm for encryption (Example
1).

FIGURE 4.10. The class Encryption provides both DES and AES algorithms for
encryption (Example 1).

There are many undesirable aspects of this design solution:


• The Encryption class becomes bigger and harder to maintain, since it
implements multiple encryption algorithms even though only one algorithm is
used at a time.
• It is difficult to add new algorithms and to vary the existing ones when a
particular encryption algorithm is an integral part of the Encryption class.
• The algorithm implementations are tied to the Encryption class, so they
cannot be reused in other contexts.
In this example, encryption algorithms provide a service to the Encryption
class. The Encryption class is tightly coupled to these services and is impacted
when they vary.
Example 2
Consider a design that supports encrypting different kinds of content (such as
images and text) as well as different kinds of algorithms (such as DES and AES).
Figure 4.11 shows a commonly-seen simple design to model this solution.

FIGURE 4.11. Hierarchy to support encryption of different kinds of content using


different kinds of encryption algorithms (Example 2).

In this design, there are two variation points: the kind of content, and the kind of
encryption algorithm that is supported. Every possible combination of these two
variations is captured in a dedicated class. For instance, DESImageEncryption
class deals with encrypting images using DES encryption algorithm. Similarly,
AESTextEncryption class deals with encrypting text using AES encryption
algorithm.
How does this design support a new type of content, a new encryption algorithm,
or both? Figure 4.12 shows how the existing design will be extended to support
TDES, a new encryption algorithm, and Data, a new type of content. Clearly, there
is an explosion of classes. This is because the variations in the implementation are
mixed together and have not been encapsulated separately, i.e., the design suffers
from Missing Encapsulation smell.
FIGURE 4.12. Class explosion in Encryption hierarchy when new kind of content
and algorithm are added (Example 2).

4.3.4 Suggested Refactoring


The generic refactoring suggestion for this smell is to encapsulate the variations.
This is often achieved in practice through the application of relevant design
patterns such as Strategy and Bridge [54].
Suggested refactoring for Example 1
Let us consider what would happen if we were to use inheritance for refactoring
Example 1. There are two options:
• Option 1: In this option, the Encryption class would inherit from
DESEncryptionAlgorithm or AESEncryptionAlgorithm as needed
and use the encrypt() method within. However, the problem with this
solution is that the Encryption class would be tied to the specific encryption
algorithm at compile time. A more serious problem is that the classes would
not share an IS-A relationship, indicating a Broken Hierarchy smell (Section
6.8) (Figure 4.13).
FIGURE 4.13. A possible refactoring for Example 1 (Option 1) which introduces
Broken Hierarchy.

• Option 2: Create subclasses named DESEncryption and AESEncryption


that extend the Encryption class and contain implementation of DES and
AES encryption algorithms, respectively. With this solution, the clients can hold
reference to an Encryption class and point to the specific subclass object
based on their need. By adding new subclasses, it is easier to add support for
new encryption algorithms as well. However, the problem with this solution is
that the DESEncryption and AESEncryption classes inherit other
methods from the Encryption class. This reduces the reusability of the
encryption algorithms in other contexts (Figure 4.14).

FIGURE 4.14. A possible refactoring for Example 1 (Option 2) using


inheritance.

A better approach is to decouple the encryption algorithms from the


Encryption class by employing the “factor out Strategy” refactoring [8]. The
resulting design structure is shown in Figure 4.15.

FIGURE 4.15. Suggested refactoring for Encryption class using Strategy pattern
(Example 1).

In this design, an EncryptionAlgorithm interface is created. Classes named


DESEncryptionAlgorithm and AESEncryptionAlgorithm implement
the EncryptionAlgorithm interface and define the DES and AES algorithms,
respectively. The Encryption class maintains a reference to the
EncryptionAlgorithm interface.
This design structure offers the following benefits:
• An Encryption object can be configured with a specific encryption
algorithm at runtime.
• The algorithms defined in the EncryptionAlgorithm hierarchy can be
reused in other contexts.
• It is easy to add support for new encryption algorithms as and when required.

Suggested refactoring for Example 2


The main issue in the design (of Example 2) is that there are two orthogonal
concerns that have been mixed up in the inheritance hierarchy. A refactoring
suggestion is to use a structure similar to the Bridge pattern [54] and encapsulate
the variations in these two concerns separately, as shown in Figure 4.16.
FIGURE 4.16. Suggested refactoring for supporting multiple kinds of content and
algorithms (Example 2).

In this solution, the hierarchy rooted in EncryptionContentType class makes


use of the algorithm implementations that are rooted in
EncryptionAlgorithm interface. If a new content type named Data and a
new encryption algorithm type TDES were to be introduced in this design, it would
result in the addition of only two new classes (as shown in the Figure 4.16). In this
way, the problem of explosion of the classes described in Example 2 can be
addressed. Also, now, the encryption algorithm implementations rooted in
EncryptionAlgorithm can be reused in other contexts.
4.3.5 Impacted Quality Attributes
• Understandability—When there are numerous services (many of which may not
be used) embedded in an abstraction, the abstraction becomes complex and
difficult to understand. Furthermore, in cases where variation is not hidden in a
hierarchy, the explosion of classes that results increases the complexity of
design. These factors impact the understandability of the design.
• Changeability and Extensibility—When variations in implementation in a type
or a hierarchy are not encapsulated separately, clients are tightly coupled to
these different variations. This has two effects when a new or different variation
must be supported. First, it is harder to change the clients when they need to
use a different or new variation. Second, it may result in explosion of classes
(see Example 2 above). If variations were to be encapsulated separately, the
clients would be shielded from changes in the variations. In the absence of
such encapsulation, changeability and extensibility are impacted.
• Reusability—When services are embedded within an abstraction, the services
cannot be reused in other contexts. When two or more orthogonal concerns
are mixed up in the hierarchy, the resulting abstractions that belong to the
hierarchy are harder to reuse. For instance, in Example 2, when encryption
content type and algorithm are mixed together, we no longer have specific
algorithm classes that could be reused in other contexts. These factors impact
the reusability of the type or hierarchy.
4.3.6 Aliases
This smell is also known in literature as:
• “Nested generalization” [52]—This smell occurs when the first level in an
inheritance hierarchy factors one generalization and the further levels
“multiplies out” all possible combinations.
• “Class explosion” [22]—This smell occurs when number of classes explode due
to extensive use of multiple generalizations.
• “Combinatorial explosion” [23]—This smell occurs when new classes need to be
added to support each new family or variation.
4.3.7 Practical Considerations
None.

Read full chapter


URL: https://www.sciencedirect.com/science/article/pii/B9780128013977000047

Introduction
Martin Reddy, in API Design for C++, 2011

1.7 About this Book


Now that I have covered the basics of what an API is and the pros and cons of API
development, I'll dive into details such as how to design good APIs, how to
implement them efficiently in C++, and how to version them without breaking
backward compatibility. The progression of chapters in this book roughly follows
the standard evolution of an API, from initial design through implementation,
versioning, documentation, and testing.
Chapter 2: Qualities
I begin the main text with a chapter that answers the following question:
what is a good API? This will cover a wide gamut of qualities that you
should be aware of when designing your APIs, such as information hiding,
minimal completeness, and loose coupling. As I do throughout the book, I
illustrate these concepts with many C++ source code examples to show
how they relate to your own projects.
Chapter 3: Patterns
The next couple of chapters tackle the question of how you design a good
API. Accordingly, Chapter 3 looks at some specific design patterns and
idioms that are particularly helpful in API design. These include the pimpl
idiom, Singleton, Factory Method, Proxy, Adapter, Façade, and Observer.
Chapter 4: Design
Continuing the topic of how to design a good API, Chapter 4 discusses
functional requirement gathering and use case modeling to drive the
design of a clean and usable interface, as well as some techniques of
object-oriented analysis and object-oriented design. This chapter also
includes a discussion on many of the problems that a large software
project faces. These observations are taken from real-world experiences
and provide insight into the issues that arise when doing large-scale API
development.
Chapter 5: Styles
The next few chapters focus on creating high-quality APIs with C++. This is
a deep and complex topic and is, of course, the specific focus of this book.
I therefore begin by describing various styles of C and C++ APIs that you
could adopt in your projects, such as flat C APIs, object-oriented APIs,
template-based APIs, and data-driven APIs.
Chapter 6: C++ Usage
Next I discuss various C++ language features that can impact good API
design. This includes numerous important issues such as good
constructor and operator style, namespaces, pointer versus reference
parameters, the use of friends, and how to export symbols in a dynamic
library.
Chapter 7: Performance
In this chapter I analyze performance issues in APIs and show you how to
build high-performing APIs in C++. This involves the use of const
references, forward declarations, data member clustering, and inlining. I
also present various tools that can help you assess the performance of
your code.
Chapter 8: Versioning
With the foundations of API design in hand, I start to expand into more
complex aspects, starting with API versioning and how to maintain
backward compatibility. This is one of the most important—and difficult
—aspects of robust API design. Here I will define the various terms
backward, forward, functional, source, and binary compatibility and
describe how to evolve an API with minimal impact to your clients.
Chapter 9: Documentation
Next I dedicate a chapter to the topic of API documentation. Because an
API is ill-defined without proper supporting documentation, I present
good techniques for commenting and documenting your API, with
specific examples using the excellent Doxygen tool.
Chapter 10: Testing
The use of extensive testing lets you evolve an API with the confidence that
you are not breaking your clients' programs. Here I present various types
of automated testing, including unit, integration, and performance tests,
and present examples of good testing methodologies for you to use in
your own projects. This covers topics such as test-driven development,
stub and mock objects, testing private code, and contract programming.
Chapter 11: Scripting
I follow this with a couple of more specialized topics, beginning with API
scripting. This is an optional subject that is not applicable to all APIs.
However, you may decide to provide scripting access to your API so that
power users of your application can write scripts to perform custom
actions. I therefore talk about how to create script bindings for a C++ API
so that it can be called from languages such as Python and Ruby.
Chapter 12: Extensibility
Another advanced topic is that of user extensibility: creating an API that
allows programmers to write custom C++ plugins that extend the basic
functionality you ship with the API. This can be a critical mechanism to
promote adoption of your API and to help it survive for the long term.
Additionally, I cover how to create extensible interfaces using inheritance
and templates.
Appendix A: Libraries
The book concludes with an appendix on how to create static and dynamic
libraries. You must be able to create libraries in order for your code to be
used by others. There are also interface design issues to consider when
creating dynamic libraries, such as the set of symbols that you export
publicly. I therefore discuss differences between static and shared libraries
and demonstrate how you can make your compiler produce these libraries
to allow the reuse of your code in other applications.

Read full chapter


URL: https://www.sciencedirect.com/science/article/pii/B9780123850034000014

Systematic methods for organising patterns for the


internet of things: A preliminary exploration
Vusi Sithole, Linda Marshall, in Internet of Things, 2020

3.2.1 Patterns in programming paradigms


In view of patterns in programming paradigms, object-oriented patterns have
become best practice due to their abstraction power and and their ability to deal
with complexity [23]. However, in the last couple of years, software problems have
become so complex to an extent that chunk hierarchies of procedure and nested
functions are no longer able to present viable solutions. In these programming
paradigms, particularly, object-oriented programming, patterns address object and
class relationships for specific design problems [23,24]. Patterns are also
considered synonymous to reusable templates containing simple understood code.
These templates often inform how code gets written and are commonly known as
idioms [25]. Object-oriented design patterns are generally classified into three
categories, namely: (i) creational design patterns such as builder, singleton, factory
method, etc., (ii) structural design patterns such as adapter, decorator, bridge, etc.,
and (iii) behavioral design patterns such as observer, strategy, chain of responsibility,
etc. In the literature, early patterns were very tightly tied to object-oriented design
because many patterns were in the form of programming language idioms. At the
time, idioms were commonly viewed as protopatterns [25]. However, there were
problems that reached far beyond the object paradigm including brokering, type
promotion, reference counting, multiple dispatch, and other memory management
schemes, etc. Nonetheless, as knowledge increased and patterns evolved, their
treatment broadened beyond the object paradigm and its idioms.
The ideas initially proposed by the Gang of Four (GoF) continue to be used in the
Software engineering community, and most authors continue to be inspired by
their line of thinking and theories. The integration messaging pattern language,
for example, is one such work that was largely influenced by the GoF. This
language consists of 65 patterns structured into 9 categories. All the patterns in
this language deal with the flow of a message from one system to another through
subscribed channels, transformations and routing [26]. This pattern language
remains relevant in the software engineering community and has been widely
adopted in other domains such as application development, cloud computing, and
the IoT. Enterprise integration patterns such as event message, publish-subscribe
channel, smart proxy, message broker, etc. are particularly predominantly used in
the IoT [12].
Today, many of the so-called design patterns in the programming paradigms are
not really object-oriented, which shows that we have progressed a bit beyond our
initial flirtations with objects. For a detailed discussion of patterns in programming
paradigms, the reader is encouraged to refer to [27] for further details.

Read full article


URL: https://www.sciencedirect.com/science/article/pii/S2542660520301025

Copyright © 2022 Elsevier B.V. or its licensors or contributors.


ScienceDirect ® is a registered trademark of Elsevier B.V.

You might also like