The document discusses various design patterns including Decorator, Proxy, Prototype, Observer, Adapter, Builder, Dependency Injection, Singleton, Memento, and Factory. For each pattern, it provides a high-level overview of how to implement the pattern programmatically, focusing on using annotations, reflection, and code generation to automate the application of the patterns. It describes generating proxy classes, intercepting method calls, deep cloning objects, notifying subscribers of changes, resolving dependencies, ensuring single instances, and other techniques.
2. Who am I?
• I’m an architect…
• …but I don’t design houses.
• I’m a professor…
• …but actually just a lecturer.
• I’m a doctor (read Ph.D.)…
• …but I don’t prescribe medication to people.
• I’m… 12.5%
Serbian
25%
Hungarian
56.25%
Romanian
6.25%
Turkish
3. Expectations
Romantic movies tend to ruin relationships.
They give women unrealistic expectations about men.
Porn movies have the same effect on men.
7. Reasons to
Automate
Design
Patterns
• Cleaner code:
• Because design patterns are applied automatically using annotations, your
application code is greatly simplified. Using design patterns results in code
that is both cleaner and more robust (see Object-Relation Mapping).
• Less Boilerplate:
• You’ll never have to write the same code twice. Design pattern automation
doesn’t just eliminate duplicate code, it also eliminates much of the code
used to integrate behaviors across a system (see Configuration Mapping).
• More Resilient Applications:
• Automatically add and remove functionality without dramatic changes to
the underlying code base. With design pattern automation, your team can
respond to fluctuating requirements more quickly, and with less code
rework (see Dependency Injection).
8. Reasons to
Automate
Design
Patterns
• Lower Cost of Development:
• Less code written means less money spent on menial development tasks.
With design pattern automation, you’ll spend more of your resources on
new functionality, not busy work (see Serialization).
• Improved Maintenance:
• Factor tracing, performance tracking, and other debugging behaviors into
your applications with design pattern automation, without changing the
source code (see Intercept Method Execution).
• Lower Maintenance Costs:
• By automating design patterns, your applications will have fewer defects,
better resiliency, improved code readability, and easier diagnostic
capabilities, making them far less costly to maintain (see Test Execution).
9. Reasons to
Automate
Design
Patterns
• Better Team Productivity:
• Moving infrastructure code into automated design patterns frees up more
team members to focus on application business logic, allowing your
company to react faster to changes in its environment (see Cloner).
• Better Rule Enforcement:
• By integrating strong validation checks into your design patterns you can
ensure team members are automatically notified of errors during
compilation, which makes good coding easier (see Data Validation).
11. Decorator
Decorator is a structural design pattern that lets you attach new behaviors to
objects by placing these objects inside special wrapper objects that contain the
behaviors.
12. Decorator • Define interfaces to decorate existing classes.
• Those decorating interfaces must extend a base interface that
has only one method called $this() which will inject the target
object.
• In the base interface implement a static method to decorate an
existing object with a given interface, returning the type of this
interface just to be able to call the new methods, while still
being able to access the target object via the $this() method.
• The implementation of this method will create a proxy using
reflection that implements the decorating interface by
dispatching the call to the default implementation of each
method, except for the $this() method, which will return the
target object used to create the invocation handler of the proxy.
• We must use unreflect special support to dispatch the call to the
default method implementation in the decorating interface,
otherwise just dispatching on the receiving proxy object will
cause entering on an infinite loop.
13. The decorator creates a proxy
to implement the extension
interface, which returns the
target object when calling
$this() method, otherwise just
dispatches the call with
unreflect special to the default
implementation of the calling
method in the extension
interface, that has access to the
target object by calling the
$this() method.
Decorator
14. Proxy
Proxy is a structural design pattern that lets you provide a substitute or
placeholder for another object. A proxy controls access to the original object,
allowing you to perform something either before or after the request gets through
to the original object.
15. Proxy • Implement a generic proxy to intercept method execution and
call before, after and throwing advices.
• Collect advices for each type and then build a new instance on
top of the target object using the enhancer from cglib to
generate a new class that extends the target type.
• This way we create a new instance that will act as a proxy to
perform the interception and then dispatch the original call to
the supporting object.
• However, due to the fact that we dynamically extend at runtime
a given class, this approach can’t intercept private methods.
• To overcome this limitation, we can use dynamic weaving of
AOP by starting the JVM with an agent which will receive the
bytecode loaded for each class, so that it can modify it by
injecting the interceptors, but this will not be a genuine proxy,
rather the proxy interception will happen only once when
loading the class.
• Proxy pattern is often used to implement interfaces locally.
16. The generic proxy with
interceptors collects before,
after and throwing advices and
uses the enhancer to sub-class
the type of the given target by
using an interceptor, and then
creates an instance of this type,
so that each time we call a
method on the proxy, this will
invoke the interceptor which
will call the set of advices on
that method while also
dispatching the call to the
target object.
Proxy
17. Prototype
Prototype is a creational design pattern that lets you copy existing objects
without making your code dependent on their classes.
18. Prototype
• Implement a deep cloning service that will return a fully copy of
a given object using reflection to inspect its containing objects.
• First, check if the source is of a primitive type and then just
return it, since primitive types are always copied being passed
by value. Then, check if the source type implements Cloneable
so that you can call its clone() method to do the job for you.
• If the source is an array, then use the reflective array support to
instantiate a new array by component type and size and access
existing array to clone each element and set it into the target.
• If the source type is a collection, then call the collection visitor
to access each element and create a clone for it that you must
add to the cloned collection.
• If the source contains sub-objects, then call the parameter-less
constructor to create the clone, visit each field, get its value,
clone it and set it as the value of the corresponding field in the
clone. Be sure you check if you haven’t already cloned it to
avoid infinite loops, by keeping a map between old and new
object reference.
19. If the object is a primitive type, just return it. If it is a String, take the character
array out of it and create a new one. If it is an array, use the reflection support
for manipulating arrays to create a new one, access each element in the
source, clone it and set the result in the clone. If it is a collection, use the add
method to collect the clone of each item in the source collection. If it
implements Cloneable, then just call the clone method. Otherwise, create a
new object and clone each field of the source into the target clone.
Prototype
20. Observer
Observer is a behavioral design pattern that lets you define a subscription
mechanism to notify multiple objects about any events that happen to the object
they’re observing.
21. Observer
• We can implement support to notify POJO property changes by
using either a class enhancer, which extends the model class
and overrides public and protected methods to call our
interceptor, or by using code injection with dynamic weaving to
inject our intercepting code on setters.
• If we implement it with class enhancer, then the objects must be
created by the observer method which extends the argument
class, attaches the interceptor on public and protected
methods, generates the subclass and then instantiates it.
• The subclass must also implement an extension interface by
which we can subscribe or unsubscribe to the property change
notifications.
• Each time we call a method on this object, the interceptor will
be called, which calls the super implementation to actually set
the property and then notify the listeners about the change.
• This solution can’t intercept private methods, which needs to
inject the interceptor via dynamic weaving and also creates a
new class.
22. We can notify property
changes on a POJO by using
cglib to create a class enhancer
which will extend the given
class while implementing the
observable interface to enable
subscriptions. This will
override all public and
protected methods of the class
which will be intercepted by
the given interceptor, so that
we will be able to collect
subscriptions and notify them
just after a setter is called.
Observer
23. Adapter
Adapter is a structural design pattern that allows objects with incompatible
interfaces to collaborate.
24. Adapter
• The adapter can be easily implemented using the proxy support
of reflection.
• This is a commonly known problem of duck-typing which says:
“If it walks like a duck and quacks like a duck, then it must be a
duck”.
• All we have to do is create a proxy to implement the given
interface we need to adapt to, and in the invocation handler just
perform the necessary actions.
• For instance, if the support object already contains methods
with the same signature as the ones in the adapting contract,
then the invocation handler will only locate the corresponding
method by name and call it.
• In this way, we can make a class implement an interface
dynamically at runtime, if the class is actually implementing the
interface but doesn’t explicitly declare this.
• The adapter could take as argument a complex logic for each
method, so that it can fit a more general approach.
25. The adapter uses reflection proxy to implement the
contract interface by calling the support object.
Adapter
26. Builder
Builder is a creational design pattern that lets you construct complex objects
step by step. The pattern allows you to produce different types and
representations of an object using the same construction code.
27. Builder • Define an annotation with Source retention and Type target.
• Implement a builder class that collects as a key-value map the
value for each property that will be used to build the final object.
• The builder class will contain methods to set and get the value
of a given property which may be called without the name of the
property, so that all calls will look the same, although they will
store or return the value of the requested property.
• In order to get the actual name of the property, we’ll get the
name of the caller method from the stack trace and take the
property name from it.
• When called to build the final object, the builder class will try to
find the appropriate constructor based on the match between
parameters and available properties, and then invoke it using
reflection.
• Implement an annotation processor to generate the code of a
builder class for each model class that has the annotation.
28. The builder uses a source and an annotation processor to scan during compilation
for annotated classes, then it uses a template file to generate a builder class that
contains methods to set and get all the properties defined by the annotated class,
which will use a temporary map for creating the model in the end.
Builder
29. The Builder utility class has generic set and get methods
to store or read a key from an internal map that is
obtained by accessing the stack trace and taking the name
of the caller method as the value of the key. The builder
also has a build method that creates the model object in
the end by trying to find the appropriate constructor with
the corresponding parameters for the collected properties
and then by settings the remaining fields on the object.
Builder
32. Dependency
Injection
Dependency Injection is a technique whereby one object supplies
the dependencies of another object. A "dependency" is an object that can be
used, for example as a service.
33. Dependency
Injection
• A dependency injection framework is based on modules that
are used by the injector, which is the object factory, to collect
binding specifications on how to resolve dependencies.
• So, we define a module interface which will be called to
configure bindings.
• A binding is a link between a type of an injectable field or
argument, and a specific type or instance that should be used
to resolve that dependency by an injection.
• So, bindings can use as resolvers either a class or an object.
• After collecting the bindings from a set of modules, the injector
can be used as an object factory to dynamically instantiate
types by calling the default parameter-less constructor and then
to resolve fields annotated with @Inject with respect to the
bindings defined by the modules.
• A dependency injection framework must also provide filter to set
a specific binding depending on the class or package context,
but for the simplicity of the implementation, we skipped this
feature.
34. Dependency
Injection
The injector is created based on a module and has a binder with which
calls the configure method of the module to collect the bindings.
35. Dependency
Injection
The injector calls the module to register bindings, then we can create objects
using its getInstance method. This will call the parameter-less constructor of the
type to create a new instance and then will search for fields annotated with
@Inject, which will be set to the value of the resolved dependency. The process
should be recursive since the resolving type may have also injectable fields.
36. Singleton
Singleton is a creational design pattern that lets you ensure that a class has
only one instance, while providing a global access point to this instance.
37. Singleton • The idea is to define a source annotation @Singleton that can
be used on classes that we expect to become singletons.
• We should implement an annotation processor which is called
during compile time and can help us trigger the code generation
that will inject the private static field to store the single instance
of the class, the getter to initialize lazy its value and a private
parameter-less constructor which should also check not to be
invoked via reflection by an inappropriate consumer.
• The problem here is that although the annotation processors
can be used to generate classes, they are forbidden to modify
the visited code, thus at this point we need to parse the
generated bytecode and inject the definition for those extra
elements by a code generation library.
• Furthermore, in order for this to work fine with IDEs that
supports incremental build, such as Eclipse, we need to
implement a bridge so that on save we’ll still have generated
code.
39. Memento
Memento is a behavioral design pattern that lets you save and restore the
previous state of an object without revealing the details of its implementation.
Show me sexy!
40. Memento • We want to be able to edit objects with transactional support.
• So, we define an interface for EditableObject that will have
methods to begin, commit, rollback or release a transaction.
• These methods are implements by default, but they need to
bind to a context in order to work, which will be done later in a
proxy.
• We then implement an adapter which creates a proxy using
reflection that will have a transaction manager to perform on it
the calls to the methods from the previously defined interface,
while the other methods will be dispatched directly on the
business object received as argument by the adapt method.
• The editable transaction manager will store in a stack different
transactions with the business object that are created and
dropped by the call of the corresponding methods in the
transactional interface.
• Each transaction stores the value of all fields in the business
object to restore it later or accept the changes on commit.
41. Memento
The editable interface is just an extension to our object
which helps managing the editing transactions. The
adapter creates a proxy to bind together the editable
interface to a transaction manager on top of the
business object.
42. Factory
Factory is a creational design pattern that provides an interface for creating
objects in a superclass, but allows subclasses to alter the type of objects that will
be created.
43. Factory • A generic serializer uses factory pattern to create object when
deserializing from a stream.
• We can implement a generic binary serializer which will store
the name of the class for each value of a field.
• This value is use to call the parameter-less constructor on
complex objects or to read using the appropriate method of a
data input stream for each primitive type.
• Array values will be deserialized by using the array support of
reflection API to create a new array of a component type with a
given size and then to set the value of each item in the array.
• Maps will be deserialized by reading each key-value pair that
are supposed to be put in a new map.
• Collections will be deserialized by reading each item and
adding it to a new collection of a type with the name saved.
44. Factory
Write type and
value for scalar
objects,
component
type and size
for array, type
and key plus
value for maps,
type and size
for collections,
type and list of
fields for
complex
objects, where
for each field
we write field
name and
value.
45. Factory
Read the number of elements in a map, collection or array. In case of a map, create a new one based
on type, then read each key-value pair and put it in the map. In case of a collection, deserialize each
element and add it to the collection instance. For an array, use reflection array support to create a
new array, deserialize each element and set it in the array.
46. Factory
In case of primitive value, we use the DataInputStream to call the appropriate read method
to get the value. For complex objects, we read the name of the type, then create an
instance of that type and read the name of each field from the input stream and set the
value of the field in the target object by reading its value from the stream.
47. Chain of
Responsibility
Chain of Responsibility is a behavioral design pattern that lets you pass
requests along a chain of handlers. Upon receiving a request, each handler
decides either to process the request or to pass it to the next handler in the
chain.
48. Chain of
Responsibility
• We can implement a chain of interceptors for a given method by
adding annotations on that method.
• Each annotation must have a handler property which will
specify the class that must implements the interceptor logic.
• We then implement a JVM agent to intercept the loading
process and take the bytecode from the instrumentation of the
class-loader and inject a call to the chain manager before the
code of each method that contains annotations with handler
property.
• When the method is called, the chain manager will execute the
handle method of the first interceptor created for the first
annotation of the method.
• Depending on the result of its execution, the current interceptor
may or may not call the next interceptor in the chain to continue
handling the call.
• If by any means an interceptor will throw and exception, this will
skip the execution of the actual logic contained by the method.
49. Chain of
Responsibility
We implement a JVM agent
with a pre-main method that
will hook our class transformer,
which will inject the call the
manager to start chaining the
interceptors.
50. Chain of
Responsibility
The chain manager scans for annotations with handlers and then instantiate
the handler for each annotation. When the method is called, then the chain
will be invoked to handle method of the first interceptor which may chain to
the next one by calling the next method of the manager. The handlers have
access to the arguments and target of the calling method.
51. Flyweight
Flyweight is a structural design pattern that lets you fit more objects into the
available amount of RAM by sharing common parts of state between multiple
objects instead of keeping all of the data in each object.
52. Flyweight • The idea is to replace heavy immutable fields by UUIDs that will
perform claim check on internal cache.
• We define a @Flyweight annotation used on heavy immutable
fields.
• We then implement an agent to intercept bytecode loading and
modify the classes.
• We remove the original field annotated with @Flyweight and
create a new field with the same name and type String that will
store only the UUID.
• We replace the setter implementation to set the value in the
cache and store the UUID in the corresponding injected field.
• We also replace the getter to take from the cache the value
based on the UUID previously stored in the corresponding field.
• This can only work if the implementation always calls getters
and setter and does not manipulate the field directly.
53. The transformer remove fields
annotated with @Flyweight
and add new ones with the
same name and type String. It
also replaces the body of
setters to set the argument in
the cache and store the UUID
in the field, and the body of
getters to return the value
from the cache based on the
UUID saved in the
corresponding field.
Flyweight
55. Parameter
Validation
Parameter Validation is the automated processing, in a module, to validate the
accuracy of parameters passed to that module. Specific best practices have
been developed, for decades, to improve the handling of such parameters.
56. Parameter
Validation
• We can define annotations to validate parameters.
• Each annotation must have a property that specifies the class
which will enforce the constraint.
• We then implement an agent to intercept the loading of
bytecode and inject a call to validate method of a validation
manager.
• This code injection is done using Javassist, that is using ASM to
modify the bytecode.
• The validate method will use reflection to instantiate the
checker of each annotation and use it to collect eventual errors.
• In case we have errors, then the validate method will trigger a
runtime error, which will skip the execution of the calling
method, if the parameters have invalid values.
• This approach can be used also for code access security,
auditing, logging, proxying, and many other scenarios that
require to intercept the execution of any private, protected or
public method.
57. Parameter
Validation
The agent has a pre-main method
which will be called to register a
class transformer, which is used to
transform the bytecode by injecting
the call to a validation manager that
should validate the parameters of
the methods that have at least one
annotated parameter with an
annotation that has a constraint
method to specify the call that will
handle the validation.
58. Parameter
Validation
The validation manager instantiate the constraint checker of each
annotation and call it to collect errors, then , if there are errors, throws
an IllegalArgumentException.
59. Remote
Procedure
Call
Remote Procedure Call is when a computer program causes a procedure to
execute on another computer, which is coded as if it were a local procedure call,
without the programmer explicitly coding the details for the remote interaction.
60. Remote
Procedure
Call
• A small framework for RPC needs a TCP server on which we
can publish services by type or by singleton instance.
• For each service we should create an instance or use the
singleton instance to dispatch the client calls.
• We need to define request object that must contain service
name, method name, arguments and session identifier and
response object that must contain return value or fault in case
of exception and the session identifier.
• On the server side we’ll implement a skeleton to instantiate and
invoke methods on service instance based on the request.
• On the client side, we need to provide implementation on the fly
of the service contract.
• This can be done by using the reflection proxy which can
implement interfaces dynamically using an invocation handler to
perform the call.
• This handler will then open the cannel, serialize the request,
wait for the response, deserialize it and return the result or fault.
61. The service tries to find service
instance by the incoming
session ID and execute the
requested method with this as
target, or null for static
methods. It has to create a new
instance if the session ID is
missing, but also to remove the
instance if the client requires to
destroy the service.
Remote
Procedure Call
62. The server worker listen for
incoming requests, deserializes
them and forwards the call to
the corresponding services.
Remote
Procedure Call
63. Remote
Procedure Call
On the client side we create
a proxy to implement service
contract based on an
invocation handler. This
handler is executed when we
call a method with the proxy
and it will open the
connection, serialize the
request, wait to read and
deserialize the response,
then throw an exception in
case a fault was received or
just return the result of the
remote method execution.
65. Infinite
Streams
Infinite Stream is a sequence of data elements made available at any arbitrary
level that can extend to an infinite collection of elements.
Normal
DNA
Italian
DNA
Hungarian
DNA
Romanian
DNA
Neighbor's
DNA
66. Infinite
Streams
An infinite stream has a head element and a supplier as the tail that can
produce the next elements in the stream. When visiting a stream, we
apply a consumer starting from the head and then advancing to the tail as
the next head to visit. This way, we call the stream supplier to produce
the next element. Same way when we take while matching a predicate,
where we continue applying the predicate on a stream starting with the
next element produced by the tail supplier. Same when filtering.
67. Infinite
Streams
• Define an interface that returns the head and tail and check if
your stream is empty.
• The interface should contain static default implemented
methods to take from a given stream while a predicate returns
true, filter the stream by a predicate and consume it with a
consumer.
• Then create a class that implements the interface to store
reference to head and tail.
• The magic of extending your stream to infinity is in the take
while method, where we create a new stream with the same
head as the source and a supplier which takes-while from the
tail with the given predicate as the extender.
• For example a counter from n can be implemented as a stream
starting from n with a supplier that returns the same counting
stream by starting from n + 1, which makes the take-while to
visit each number that follows the starting one.
68. Infinite
Streams
Counting stream from n starts with n as head and has a tail which is a supplier that takes the steam starting with
n + 1. Sieve of Eratosthenes, which is a sequence of prime numbers, starts with a prime number and eliminates
the following numbers which divide by this prime number, which will let in the stream the next prime number
with which we start searching for the next element in the stream and so on. Fibonacci stream has a supplier that
takes the previous two values, so the head is a supplier of two fixed values for 0 and 1.
70. Advantages
âś“ Clean and easy to read code.
âś“ Less duplicate and boilerplate code.
âś“ Easy to add new functionality.
âś“ Less time spent on new development.
âś“ Better maintenance of application code.
âś“ Better team productivity.
âś“ Lower development costs.
71. Disadvantages
âś–Can make code performs non-intuitive.
âś–Can make application difficult to debug.
âś–May slow-down application performances.
âś–May increase security risks.
âś–Requires deep understanding of program
execution model.
72. Homework
Try to find for yourself use-case where you might need to automate design
patterns and implement them with the tools that we practiced by these
examples.