๐Ÿš€ UllrichLumina

Design Patterns web based applications closed

Design Patterns web based applications closed

๐Ÿ“… | ๐Ÿ“‚ Category: Java

Building robust and maintainable web applications requires a deep understanding of architectural principles. Design patterns, proven solutions to recurring design problems, are crucial for developers aiming to create scalable, efficient, and adaptable web applications. Leveraging these patterns can significantly reduce development time, improve code readability, and minimize technical debt. This article explores essential design patterns for web applications, providing practical insights and examples to help you build better software.

Model-View-Controller (MVC)

MVC is a fundamental architectural pattern that separates concerns into three interconnected components: the Model (data), the View (presentation), and the Controller (logic). This separation promotes code organization, simplifies testing, and facilitates parallel development. Think of an e-commerce website. The Model represents product details, the View displays these details on the product page, and the Controller handles user interactions like adding items to a cart.

MVC’s strength lies in its clear delineation of responsibilities. Changes to one component have minimal impact on others, leading to a more manageable and maintainable codebase. For instance, updating the product display (View) doesn’t necessitate modifications to the data handling logic (Model).

Singleton Pattern

The Singleton pattern restricts the instantiation of a class to a single object, ensuring global access. This is useful for managing shared resources like database connections or configuration settings. Imagine a logging system where you want a single instance to handle all log entries throughout your application. The Singleton pattern ensures that all components interact with the same logger.

While useful, overuse of the Singleton pattern can lead to tight coupling and hinder testability. It’s crucial to use it judiciously, primarily for managing resources where a single point of access is essential.

Factory Pattern

The Factory pattern defines an interface for creating objects but lets subclasses decide which class to instantiate. This promotes loose coupling by abstracting the object creation process. Consider a payment gateway integration where you need to support multiple payment methods (e.g., credit card, PayPal). A factory can handle the creation of the appropriate payment processor based on user selection.

By centralizing object creation, the Factory pattern simplifies code maintenance and makes it easier to add new payment methods without modifying existing code. This flexibility is invaluable in evolving web applications.

Observer Pattern

The Observer pattern establishes a one-to-many dependency between objects. When one object (the subject) changes state, all its dependents (observers) are notified and updated automatically. This is commonly used in UI updates, event handling, and real-time data synchronization. For example, a live chat application can use the Observer pattern to update all participants when a new message is sent.

The Observer pattern promotes loose coupling by decoupling the subject from its observers. The subject doesn’t need to know the specific details of its observers, only that they implement a specific interface.

Strategy Pattern

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This allows you to select the appropriate algorithm at runtime based on specific needs. Consider a sorting algorithm where you want to switch between different sorting methods (e.g., bubble sort, quicksort). The Strategy pattern allows you to easily swap algorithms without altering the client code.

By encapsulating each algorithm, the Strategy pattern promotes code reusability and simplifies maintenance. It’s particularly useful when dealing with multiple variations of a specific operation.

  • Key takeaway 1: Design patterns offer proven solutions for common web application development challenges.
  • Key takeaway 2: Selecting the right pattern is crucial for maximizing its benefits.
  1. Identify the problem.
  2. Research applicable patterns.
  3. Implement the chosen pattern.

“Design patterns are not a silver bullet, but they provide valuable guidance for building maintainable and scalable web applications.” - Unknown

For more in-depth information on web development best practices, visit W3Schools.

Learn More About Design PatternsFeatured Snippet: Design patterns are reusable solutions to common software design problems. They provide a structured approach to building robust and maintainable applications.

[Infographic Placeholder]

FAQ

Q: What are the benefits of using design patterns?

A: Design patterns enhance code organization, improve maintainability, and facilitate collaboration among developers.

By understanding and applying these core design patterns, you can significantly elevate the quality and maintainability of your web applications. Implementing these patterns helps create more robust, scalable, and adaptable software that can handle evolving requirements. Explore further resources like Refactoring.guru and SourceMaking.com to deepen your understanding and refine your skills in applying these essential design patterns. Consider joining online communities and forums dedicated to software development to discuss best practices and share your experiences with other developers navigating the complexities of building modern web applications. This collaborative approach will further enhance your understanding and contribute to the continuous improvement of your development skills.

Question & Answer :

I am designing a simple web-based application. I am new to this web-based domain.I needed your advice regarding the design patterns like how responsibility should be distributed among Servlets, criteria to make new Servlet, etc.

Actually, I have few entities on my home page and corresponding to each one of them we have few options like add, edit and delete. Earlier I was using one Servlet per options like Servlet1 for add entity1, Servlet2 for edit entity1 and so on and in this way we ended up having a large number of servlets.

Now we are changing our design. My question is how you exactly choose how you choose the responsibility of a servlet. Should we have one Servlet per entity which will process all it’s options and forward request to the service layer. Or should we have one servlet for the whole page which will process the whole page request and then forward it to the corresponding service layer? Also, should the request object forwarded to service layer or not.

A bit decent web application consists of a mix of design patterns. I’ll mention only the most important ones.


Model View Controller pattern

The core (architectural) design pattern you’d like to use is the Model-View-Controller pattern. The Controller is to be represented by a Servlet which (in)directly creates/uses a specific Model and View based on the request. The Model is to be represented by Javabean classes. This is often further dividable in Business Model which contains the actions (behaviour) and Data Model which contains the data (information). The View is to be represented by JSP files which have direct access to the (Data) Model by EL (Expression Language).

Then, there are variations based on how actions and events are handled. The popular ones are:

  • Request (action) based MVC: this is the simplest to implement. The (Business) Model works directly with HttpServletRequest and HttpServletResponse objects. You have to gather, convert and validate the request parameters (mostly) yourself. The View can be represented by plain vanilla HTML/CSS/JS and it does not maintain state across requests. This is how among others Spring MVC, Struts and Stripes works.
  • Component based MVC: this is harder to implement. But you end up with a simpler model and view wherein all the “raw” Servlet API is abstracted completely away. You shouldn’t have the need to gather, convert and validate the request parameters yourself. The Controller does this task and sets the gathered, converted and validated request parameters in the Model. All you need to do is to define action methods which works directly with the model properties. The View is represented by “components” in flavor of JSP taglibs or XML elements which in turn generates HTML/CSS/JS. The state of the View for the subsequent requests is maintained in the session. This is particularly helpful for server-side conversion, validation and value change events. This is how among others JSF, Wicket and Play! works.

As a side note, hobbying around with a homegrown MVC framework is a very nice learning exercise, and I do recommend it as long as you keep it for personal/private purposes. But once you go professional, then it’s strongly recommended to pick an existing framework rather than reinventing your own. Learning an existing and well-developed framework takes in long term less time than developing and maintaining a robust framework yourself.

In the below detailed explanation I’ll restrict myself to request based MVC since that’s easier to implement.


Front Controller pattern (Mediator pattern)

First, the Controller part should implement the Front Controller pattern (which is a specialized kind of Mediator pattern). It should consist of only a single servlet which provides a centralized entry point of all requests. It should create the Model based on information available by the request, such as the pathinfo or servletpath, the method and/or specific parameters. The Business Model is called Action in the below HttpServlet example.

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { Action action = ActionFactory.getAction(request); String view = action.execute(request, response); if (view.equals(request.getPathInfo().substring(1)) { request.getRequestDispatcher("/WEB-INF/" + view + ".jsp").forward(request, response); } else { response.sendRedirect(view); // We'd like to fire redirect in case of a view change as result of the action (PRG pattern). } } catch (Exception e) { throw new ServletException("Executing action failed.", e); } } 

Executing the action should return some identifier to locate the view. Simplest would be to use it as filename of the JSP. Map this servlet on a specific url-pattern in web.xml, e.g. /pages/*, *.do or even just *.html.

In case of prefix-patterns as for example /pages/* you could then invoke URL’s like http://example.com/pages/register, http://example.com/pages/login, etc and provide /WEB-INF/register.jsp, /WEB-INF/login.jsp with the appropriate GET and POST actions. The parts register, login, etc are then available by request.getPathInfo() as in above example.

When you’re using suffix-patterns like *.do, *.html, etc, then you could then invoke URL’s like http://example.com/register.do, http://example.com/login.do, etc and you should change the code examples in this answer (also the ActionFactory) to extract the register and login parts by request.getServletPath() instead.


Strategy pattern

The Action should follow the Strategy pattern. It needs to be defined as an abstract/interface type which should do the work based on the passed-in arguments of the abstract method (this is the difference with the Command pattern, wherein the abstract/interface type should do the work based on the arguments which are been passed-in during the creation of the implementation).

public interface Action { public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception; } 

You may want to make the Exception more specific with a custom exception like ActionException. It’s just a basic kickoff example, the rest is all up to you.

Here’s an example of a LoginAction which (as its name says) logs in the user. The User itself is in turn a Data Model. The View is aware of the presence of the User.

public class LoginAction implements Action { public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception { String username = request.getParameter("username"); String password = request.getParameter("password"); User user = userDAO.find(username, password); if (user != null) { request.getSession().setAttribute("user", user); // Login user. return "home"; // Redirect to home page. } else { request.setAttribute("error", "Unknown username/password. Please retry."); // Store error message in request scope. return "login"; // Go back to redisplay login form with error. } } } 

Factory method pattern

The ActionFactory should follow the Factory method pattern. Basically, it should provide a creational method which returns a concrete implementation of an abstract/interface type. In this case, it should return an implementation of the Action interface based on the information provided by the request. For example, the method and pathinfo (the pathinfo is the part after the context and servlet path in the request URL, excluding the query string).

public static Action getAction(HttpServletRequest request) { return actions.get(request.getMethod() + request.getPathInfo()); } 

The actions in turn should be some static/applicationwide Map<String, Action> which holds all known actions. It’s up to you how to fill this map. Hardcoding:

actions.put("POST/register", new RegisterAction()); actions.put("POST/login", new LoginAction()); actions.put("GET/logout", new LogoutAction()); // ... 

Or configurable based on a properties/XML configuration file in the classpath: (pseudo)

for (Entry entry : configuration) { actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance()); } 

Or dynamically based on a scan in the classpath for classes implementing a certain interface and/or annotation: (pseudo)

for (ClassFile classFile : classpath) { if (classFile.isInstanceOf(Action.class)) { actions.put(classFile.getAnnotation("mapping"), classFile.newInstance()); } } 

Keep in mind to create a “do nothing” Action for the case there’s no mapping. Let it for example return directly the request.getPathInfo().substring(1) then.


Other patterns

Those were the important patterns so far.

To get a step further, you could use the Facade pattern to create a Context class which in turn wraps the request and response objects and offers several convenience methods delegating to the request and response objects and pass that as argument into the Action#execute() method instead. This adds an extra abstract layer to hide the raw Servlet API away. You should then basically end up with zero import javax.servlet.* declarations in every Action implementation. In JSF terms, this is what the FacesContext and ExternalContext classes are doing. You can find a concrete example in this answer.

Then there’s the State pattern for the case that you’d like to add an extra abstraction layer to split the tasks of gathering the request parameters, converting them, validating them, updating the model values and execute the actions. In JSF terms, this is what the LifeCycle is doing.

Then there’s the Composite pattern for the case that you’d like to create a component based view which can be attached with the model and whose behaviour depends on the state of the request based lifecycle. In JSF terms, this is what the UIComponent represent.

This way you can evolve bit by bit towards a component based framework.


See also: