Software is a craft and designing is the heart of it.
While there can be supporters of different ways of designing a system like "create the design first" or "letting the design evolve" and pros and cons of either ways.. but, considering the agile way of software making (and evolving requirements), it is essential to inculcate the value of "evolving design" in software making.
Even if we have a design, it is bound to evolve if it needs to address the new requirements or when we unearth something which the design can't address while implementing the requirements.. In this aspect, refactoring is extremely important and TDD is a definite way to enable "fast refactoring" by creating faster feedback cycles.
While software design is all about keeping things clean, it is also about ability to make changes to the software so that it can address the unseen future requirements easily.
It is critical to know/foresee the problems of the software and minimise them .. While the ways of designing can be differ, there is something which is common in both manners of designing.. that is applying the design patterns... There can be a middle ground struck in the ways where an initial minimal design is created to understand the problem domain and devise the possible solutions and then, implementing the solution by TDD where you let the design freely evolve and also, embellish the thought out design with actual problem solutions encountered in the process.
Irrespective of the designing methodology, it is important to know the common problems software presents and the solutions to these common problems.. Welcome to "Design Patterns" !!
It is important to know the context and the pattern(s) applicable for the problems that might surface in the software. Below is a brief summary of the patterns from the GoF pattern catalog:
Creational
Singleton: If we need only one instance of a class - either due to cost of the resource creation or nature of the business entity. E.g. ConnectionFactory, HibernateConfiguration, java.lang.RunTime.
Factory: Avoid tight coupling the client and specific object/handler, implement single responsibility principle for creating classes, . E.g. Creating Chess Pieces, Calendar.getInstance(), ResourceBundle.getBundle().
Abstract Factory: Creation of different objects, usually of same type but multiple families. E.g. Theme UI Controls(Buttons, Text box, Checkbox, etc), javax.xml.parsers.DocumentBuilderFactory#newInstance().
Builder: Allows you to produce different types and representations of an object using the same construction code. Lets you construct complex objects step by step. This is applicable to get rid of the telescopic constructors for a class. e.g. StringBuilder.append(), StringBuffer.append(), or below code snippet:
AmazonS3 s3client = AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(Regions.US_EAST_2)
.build();
Prototype: If an object creation is costly/time-consuming, the new objects can be created using existing objects. It can be implemented using Prototype and Prototype registry. Example of this is "java.lang.Object#clone()" (the class has to implement java.lang.Cloneable)
Structural
Adapter: To avoid tight coupling with third party libraries or code which cannot be modified or is not part of the code base, Adapter pattern is recommended. Example: Wrapping a third party logging library in an application class, java.util.Arrays#asList().
Composite: An abstraction which represents "Is-A" as well as a "Has-A" relationship. This represents a part-whole hierarchy. E.g. Query Conditions, which can be nested.
Decorator: A class which wraps a type of itself. This is usually doing additional processing on top of the processing done by the complete chain of wrapped classes. E.g. java.io.InputStream, java.io.OutputStream, real life examples could be Pizza with toppings and cheese, different types of sandwitches, Ice-creams etc.
Bridge: Decouple an abstraction from its implementation so that two can vary independently. Example of this could be Message/MessageSender(abstraction/implementor) or Vehicle/VehicleManufacturer(abstraction/implementor). Message can be of type TextMessage/EmailMessage and similar hierarchy for TextMessageSender/EmailMessageSender or Vehicle can be Car/Bike and similar hierarchy for CarWorkshop/BikeWorkshop.
Flyweight: It is a shared object that can be used at multiple places with a distinction of intrinsic and extrinsic state. Example of this Integer.valueOf (similarly Boolean, Byte, Long, etc) and chess pieces of rook, knight, bishop, pawns.
To be explored more: Facade, Proxy.
Behavioural
Strategy: Encapsulate a family of algorithms and let the clients use them interchangeably. e.g. java.util.Comparator#compare(), executed by among others Collections#sort(), movement rules of different chess pieces.
Template Method: Create a skeleton/template of an algorithm and defer the details/logic of some step(s) to subclasses. Example : Struts used this for Action.
Visitor: Represents operation(s) for elements of object structure (E.g. a class hierarchy) which is typically not the responsibility of the object structure. This allows addition of new operation without change in the object structure. This is also called as a 'double dispatch' pattern since it displays polymorphism twice - once as a type of visitor and once as a particular class in the structure.
State: This lets an object change its behaviour based on its intrinsic state. e.g. javax.faces.lifecycle.LifeCycle#execute() (controlled by FacesServlet, the behaviour is dependent on current phase (state) of JSF lifecycle).
Observer: This is a publisher-subscriber pattern and is typical to represent one-to-many relation between an object and dependents for any state change in the object. E.g. javax.servlet.http.HttpSessionBindingListener, javax.servlet.http.HttpSessionAttributeListener
Chain Of Responsibility: This pattern decouples the sender from the receiver/handlers of a request and typically, has source of request and a series of receivers/handlers. E.g. javax.servlet.Filter#doFilter(), AuthenticationChain, Permission/Visibility Chain, etc.
Command: This pattern encapsulates a request as an object, decouples a sender and a receiver, allows to parameterise clients/methods with different requests. This is typically useful for queueing, scheduling or logging of requests and supports undo operations very well. This is also particularly useful if same command is used at many places like Copying with button, context menu and a shortcut key. Example of this is implementations of java.lang.Runnable.
To be explored more: Interpreter, Memento, Mediator, Iterator.
While there can be supporters of different ways of designing a system like "create the design first" or "letting the design evolve" and pros and cons of either ways.. but, considering the agile way of software making (and evolving requirements), it is essential to inculcate the value of "evolving design" in software making.
Even if we have a design, it is bound to evolve if it needs to address the new requirements or when we unearth something which the design can't address while implementing the requirements.. In this aspect, refactoring is extremely important and TDD is a definite way to enable "fast refactoring" by creating faster feedback cycles.
While software design is all about keeping things clean, it is also about ability to make changes to the software so that it can address the unseen future requirements easily.
It is critical to know/foresee the problems of the software and minimise them .. While the ways of designing can be differ, there is something which is common in both manners of designing.. that is applying the design patterns... There can be a middle ground struck in the ways where an initial minimal design is created to understand the problem domain and devise the possible solutions and then, implementing the solution by TDD where you let the design freely evolve and also, embellish the thought out design with actual problem solutions encountered in the process.
Irrespective of the designing methodology, it is important to know the common problems software presents and the solutions to these common problems.. Welcome to "Design Patterns" !!
It is important to know the context and the pattern(s) applicable for the problems that might surface in the software. Below is a brief summary of the patterns from the GoF pattern catalog:
Creational
Singleton: If we need only one instance of a class - either due to cost of the resource creation or nature of the business entity. E.g. ConnectionFactory, HibernateConfiguration, java.lang.RunTime.
Factory: Avoid tight coupling the client and specific object/handler, implement single responsibility principle for creating classes, . E.g. Creating Chess Pieces, Calendar.getInstance(), ResourceBundle.getBundle().
Abstract Factory: Creation of different objects, usually of same type but multiple families. E.g. Theme UI Controls(Buttons, Text box, Checkbox, etc), javax.xml.parsers.DocumentBuilderFactory#newInstance().
Builder: Allows you to produce different types and representations of an object using the same construction code. Lets you construct complex objects step by step. This is applicable to get rid of the telescopic constructors for a class. e.g. StringBuilder.append(), StringBuffer.append(), or below code snippet:
AmazonS3 s3client = AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(Regions.US_EAST_2)
.build();
Structural
Adapter: To avoid tight coupling with third party libraries or code which cannot be modified or is not part of the code base, Adapter pattern is recommended. Example: Wrapping a third party logging library in an application class, java.util.Arrays#asList().
Composite: An abstraction which represents "Is-A" as well as a "Has-A" relationship. This represents a part-whole hierarchy. E.g. Query Conditions, which can be nested.
Decorator: A class which wraps a type of itself. This is usually doing additional processing on top of the processing done by the complete chain of wrapped classes. E.g. java.io.InputStream, java.io.OutputStream, real life examples could be Pizza with toppings and cheese, different types of sandwitches, Ice-creams etc.
Bridge: Decouple an abstraction from its implementation so that two can vary independently. Example of this could be Message/MessageSender(abstraction/implementor) or Vehicle/VehicleManufacturer(abstraction/implementor). Message can be of type TextMessage/EmailMessage and similar hierarchy for TextMessageSender/EmailMessageSender or Vehicle can be Car/Bike and similar hierarchy for CarWorkshop/BikeWorkshop.
Flyweight: It is a shared object that can be used at multiple places with a distinction of intrinsic and extrinsic state. Example of this Integer.valueOf (similarly Boolean, Byte, Long, etc) and chess pieces of rook, knight, bishop, pawns.
To be explored more: Facade, Proxy.
Behavioural
Strategy: Encapsulate a family of algorithms and let the clients use them interchangeably. e.g. java.util.Comparator#compare(), executed by among others Collections#sort(), movement rules of different chess pieces.
Template Method: Create a skeleton/template of an algorithm and defer the details/logic of some step(s) to subclasses. Example : Struts used this for Action.
Visitor: Represents operation(s) for elements of object structure (E.g. a class hierarchy) which is typically not the responsibility of the object structure. This allows addition of new operation without change in the object structure. This is also called as a 'double dispatch' pattern since it displays polymorphism twice - once as a type of visitor and once as a particular class in the structure.
State: This lets an object change its behaviour based on its intrinsic state. e.g. javax.faces.lifecycle.LifeCycle#execute() (controlled by FacesServlet, the behaviour is dependent on current phase (state) of JSF lifecycle).
Observer: This is a publisher-subscriber pattern and is typical to represent one-to-many relation between an object and dependents for any state change in the object. E.g. javax.servlet.http.HttpSessionBindingListener, javax.servlet.http.HttpSessionAttributeListener
Chain Of Responsibility: This pattern decouples the sender from the receiver/handlers of a request and typically, has source of request and a series of receivers/handlers. E.g. javax.servlet.Filter#doFilter(), AuthenticationChain, Permission/Visibility Chain, etc.
Command: This pattern encapsulates a request as an object, decouples a sender and a receiver, allows to parameterise clients/methods with different requests. This is typically useful for queueing, scheduling or logging of requests and supports undo operations very well. This is also particularly useful if same command is used at many places like Copying with button, context menu and a shortcut key. Example of this is implementations of java.lang.Runnable.
To be explored more: Interpreter, Memento, Mediator, Iterator.
No comments:
Post a Comment