2
Writing tightly coupled code

In this chapter

  • Writing a tightly coupled application
  • Evaluating the composability of that application
  • Analyzing the lack of composability in that application

As we mentioned in chapter 1, a sauce béarnaise is an emulsified sauce made from egg yolk and butter, but this doesn’t magically instill in you the ability to make one. The best way to learn is to practice, but an example can often bridge the gap between theory and practice. Watching a professional cook making a sauce béarnaise is helpful before you try it out yourself.

When we introduced Dependency Injection (DI) in the last chapter, we presented a high-level tour to help you understand its purpose and general principles. But that simple explanation doesn’t do justice to DI. DI is a way to enable loose coupling, and loose coupling is first and foremost an efficient way to deal with complexity.

Most software is complex in the sense that it must address many issues simultaneously. Besides the business concerns, which may be complex in their own right, software must also address matters related to security, diagnostics, operations, performance, and extensibility. Instead of addressing all of these concerns in one big ball of mud, loose coupling encourages you to address each concern separately. It’s easier to address each in isolation, but ultimately, you must still compose this complex set of issues into a single application.

In this chapter, we’ll take a look at a more complex example. You’ll see how easy it is to write tightly coupled code. You’ll also join us in an analysis of why tightly coupled code is problematic from a maintainability perspective. In chapter 3, we’ll use DI to completely rewrite this tightly coupled code base to one that’s loosely coupled. If you want to see loosely coupled code right away, you may want to skip this chapter. If not, when you’re done with this chapter, you should begin to understand what it is that makes tightly coupled code so problematic.

2.1 Building a tightly coupled application

The idea of building loosely coupled code isn’t particularly controversial, but there’s a huge gap between theory and practice. Before we show you in the next chapter how to use DI to build a loosely coupled application, we want to show you how easily it can go wrong. A common attempt at loosely coupled code is building a layered application. Anyone can draw a three-layer application diagram, and figure 2.1 proves that we can too.

02-01.eps

Figure 2.1 Standard three-layer application architecture. This is the simplest and most common variation of the n-layer application architecture, whereby an application is composed of n distinct layers.

Drawing a three-layer diagram is deceptively simple, but the act of drawing the diagram is akin to stating that you’ll have sauce béarnaise with your steak: it’s a declaration of intent that carries no guarantee with regard to the final result. You can end up with something else, as you shall soon see.

There’s more than one way to view and design a flexible and maintainable complex application, but the n-layer application architecture constitutes a well-known, tried-and-tested approach. The challenge is to implement it correctly. Armed with a three-layer diagram like the one in figure 2.1, you can start building an application.

2.1.1 Meet Mary Rowan

Mary Rowan is a professional .NET developer working for a local Certified Microsoft Partner that mainly develops web applications. She’s 34 years old and has been working with software for 11 years. This makes her one of the more experienced developers in the company. In addition to performing her regular duties as a senior developer, she often acts as a mentor for junior developers. In general, Mary is happy about the work that she’s doing, but it frustrates her that milestones are often missed, forcing her and her colleagues to work long hours and weekends to meet deadlines.

She suspects that there must be more efficient ways to build software. In an effort to learn about efficiency, she buys a lot of programming books, but she rarely has time to read them, as much of her spare time is spent with her husband and two girls. Mary likes to go hiking in the mountains. She’s also an enthusiastic cook, and she definitely knows how to make a real sauce béarnaise.

Mary has been asked to create a new e-commerce application on ASP.NET Core MVC and Entity Framework Core with SQL Server as the data store. To maximize modularity, it must be a three-layer application.

The first feature to implement should be a simple list of featured products, pulled from a database table and displayed on a web page (an example is shown in figure 2.2). And, if the user viewing the list is a preferred customer, the price on all products should be discounted by 5%.

02-02.tif

Figure 2.2 Screen capture of the e-commerce web application Mary has been asked to develop. It features a simple list of featured products and their prices.

To complete her first feature, Mary will have to implement the following:

  • A data layer — Includes a Products table in the database, which represents all database rows, and a Product class, which represents a single database row
  • A domain layer — Contains the logic for retrieving the featured products
  • A UI Layer with an MVC controller — Handles incoming requests, retrieves the relevant data from the domain layer, and sends it to the Razor view, which eventually renders the list of featured products

Let’s look over Mary’s shoulder as she implements the application’s first feature.

2.1.2 Creating the data layer

Because Mary will need to pull data from a database table, she has decided to begin by implementing the data layer. The first step is to define the database table itself. Mary uses SQL Server Management Studio to create the table shown in table 2.1.

Table 2.1 Mary creates the Products table with the following columns.
Column NameData TypeAllow NullsPrimary Key
IduniqueidentifierNoYes
Namenvarchar(50)NoNo
Descriptionnvarchar(max)NoNo
UnitPricemoneyNoNo
IsFeaturedbitNoNo

To implement the data access layer, Mary adds a new library to her solution. The following listing shows her Product class.

Listing 2.1 Mary’s Product class

public class Product
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal UnitPrice { get; set; }
    public bool IsFeatured { get; set; }
}

Mary uses Entity Framework for her data access needs. She adds a dependency to the Microsoft.EntityFrameworkCore.SqlServer NuGet package to her project, and implements an application-specific DbContext class that allows her application to access the Products table via the CommerceContext class. The following listing shows her CommerceContext class.

Listing 2.2 Mary’s CommerceContext class

public class CommerceContext : Microsoft.EntityFrameworkCore.DbContext
{
    public DbSet<Product> Products { get; set; }    ①  

    protected override void OnConfiguring(    ②  
        DbContextOptionsBuilder builder)
    {
        var config = new ConfigurationBuilder()    ③  
            .SetBasePath(    ③  
                Directory.GetCurrentDirectory())    ③  
            .AddJsonFile("appsettings.json")    ③  
            .Build();    ③  

        string connectionString =    ④  
            config.GetConnectionString(    ④  
                "CommerceConnectionString");    ④  
    ④  
        builder.UseSqlServer(connectionString);    ④  
    }
}

Because CommerceContext loads a connection string from a configuration file, that file needs to be created. Mary adds a file named appsettings.json to her web project, with the following content:

{
  "ConnectionStrings": {
    "CommerceConnectionString":
      "Server=.;Database=MaryCommerce;Trusted_Connection=True;"
  }
}

CommerceContext and Product are public types contained within the same assembly. Mary knows that she’ll later need to add more features to her application, but the data access component required to implement the first feature is now completed (figure 2.3).

02-03.eps

Figure 2.3 How far Mary has come in implementing the layered architecture envisioned in figure 2.1.

Now that the data access layer has been implemented, the next logical step is the domain layer. The domain layer is also referred to as the domain logic layer, business layer, or business logic layer. Domain logic is all the behavior that the application needs to have, specific to the domain the application is built for.

2.1.3 Creating the domain layer

With the exception of pure data-reporting applications, there’s always domain logic. You may not realize it at first, but as you get to know the domain, its embedded and implicit rules and assumptions will gradually emerge. In the absence of any domain logic, the list of products exposed by CommerceContext could technically have been used directly from the UI layer.

The requirements for Mary’s application state that preferred customers should be shown the product list prices with a 5% discount. Mary has yet to figure out how to identify a preferred customer, so she asks her coworker Jens for advice:

Mary: I need to implement this business logic so that a preferred customer gets a 5% discount.

Jens: Sounds easy. Just multiply by .95.

Mary: Thanks, but that’s not what I wanted to ask you about. What I wanted to ask you is, how should I identify a preferred customer?

Jens: I see. Is this a web application or a desktop application?

Mary: It’s a web app.

Jens: Okay, then you can use the User property of the HttpContext to check if the current user is in the role PreferredCustomer.

Mary: Slow down, Jens. This code must be in the domain layer. It’s a library. There’s no HttpContext.

Jens: Oh. [Thinks for a while] I still think you should use the HttpContext of ASP.NET to look up the value for the user. You can then pass the value to your domain logic as a boolean.

Mary: I don’t know...

Jens: That’ll also ensure that you have good Separation of Concerns because your domain logic doesn’t have to deal with security. You know, the Single Responsibility Principle! It’s the Agile way to do it!

Mary: I guess you’ve got a point.

Jens is basing his advice on his technical knowledge of ASP.NET. As the discussion takes him away from his comfort zone, he steamrolls Mary with a triple combo of buzzwords. Be aware that Jens doesn’t know what he’s talking about:

  • He misuses the concept of Separation of Concerns. Although it’s important to separate security concerns from the domain logic, moving this to the presentation layer doesn’t help in separating concerns.
  • He only mentions Agile because he recently heard someone else talk enthusiastically about it.
  • He completely misses the point of the Single Responsibility Principle. Although the quick feedback cycle that Agile methodologies provide can help you improve your software design accordingly, by itself, the Single Responsibility Principle as a software design principle is independent of the chosen software development methodology.

Armed with Jens’ unfortunately poor advice, Mary creates a new C# library project and adds a class called ProductService, shown in listing 2.3. To make the ProductService class compile, she must add a reference to her data access layer, because the CommerceContext class is defined there.

Listing 2.3 Mary’s ProductService class

public class ProductService
{
    private readonly CommerceContext dbContext;

    public ProductService()
    {
        this.dbContext = new CommerceContext();    ①  
    }

    public IEnumerable<Product> GetFeaturedProducts(
        bool isCustomerPreferred)
    {
        decimal discount =
            isCustomerPreferred ? .95m : 1;

        var featuredProducts =    ②  
            from product in this.dbContext.Products    ②  
            where product.IsFeatured    ②  
            select product;

        return
            from product in    ③  
                featuredProducts.AsEnumerable()    ③  
            select new Product    ③  
            {    ③  
                Id = product.Id,    ③  
                Name = product.Name,    ③  
                Description = product.Description,    ③  
                IsFeatured = product.IsFeatured,    ③  
                UnitPrice =    ③  
                    product.UnitPrice * discount    ③  
            };
    }
}

Mary’s happy that she has encapsulated the data access technology (Entity Framework Core), configuration, and domain logic in the ProductService class. She has delegated the knowledge of the user to the caller by passing in the isCustomerPreferred parameter, and she uses this value to calculate the discount for all the products.

Further refinement could include replacing the hard-coded discount value (.95) with a configurable number, but, for now, this implementation will suffice. Mary’s almost done. The only thing still left is the UI layer. Mary decides that it can wait until tomorrow. Figure 2.4 shows how far Mary has come with implementing the architecture envisioned in figure 2.1.

02-04.eps

Figure 2.4 Compared to figure 2.3, Mary has now implemented the data access layer and the domain layer. The UI layer still remains to be implemented.

What Mary doesn’t realize is that by letting the ProductService depend on the data access layer’s CommerceContext class, she tightly coupled her domain layer to the data access layer. We’ll explain what’s wrong with that in section 2.2.

2.1.4 Creating the UI layer

The next day, Mary resumes her work with the e-commerce application, adding a new ASP.NET Core MVC application to her solution. Don’t worry if you aren’t familiar with the ASP.NET Core MVC framework. The intricate details of how the MVC framework operates aren’t the focus of this discussion. The important part is how Dependencies are consumed, and that’s a relatively platform-neutral subject.

The next listing shows how Mary implements an Index method on her HomeController class to extract the featured products from the database and pass them to the view. To make this code compile, she must add references to both the data access layer and the domain layer. This is because the ProductService class is defined in the domain layer, but the Product class is defined in the data access layer.

Listing 2.4 Index method on the default controller class

public ViewResult Index()
{
    bool isPreferredCustomer =    ①  
        this.User.IsInRole("PreferredCustomer");    ①  

    var service = new ProductService();    ②  

    var products = service.GetFeaturedProducts(    ③  
        isPreferredCustomer);    ③  

    this.ViewData["Products"] = products;    ④  

    return this.View();
}

As part of the ASP.NET Core MVC lifecycle, the User property on the HomeController class is automatically populated with the correct user object, so Mary uses it to determine if the current user is a preferred customer. Armed with this information, she can invoke the domain logic to get the list of featured products.

In Mary’s application, the list of products must be rendered by the Index view. The following listing shows the markup for the view.

Listing 2.5 Index view markup

<h2>Featured Products</h2>
<div>
@{
    var products =    ①  
        (IEnumerable<Product>)this.ViewData["Products"];    ①  

    foreach (Product product in products)    ②  
    {
        <div>@product.Name (@product.UnitPrice.ToString("C"))</div>
    }
}
</div>

ASP.NET Core MVC lets you write standard HTML with bits of imperative code embedded to access objects created and assigned by the controller that created the view. In this case, the HomeController’s Index method assigned the list of featured products to a key called Products that Mary uses in the view to render the list of products. Figure 2.5 shows how Mary has now implemented the architecture envisioned in figure 2.1.

02-05.eps

Figure 2.5 Mary has now implemented all three layers in the application.

With all three layers in place, the applications should theoretically work. But only by running the application can she verify whether that’s the case.

2.2 Evaluating the tightly coupled application

Mary has now implemented all three layers, so it’s time to see if the application works. She presses F5 and the web page shown in figure 2.2 appears. The Featured Products feature is now done, and Mary feels confident and ready to implement the next feature in the application. After all, she followed established best practices and created a three-layer application ... or did she?

Did Mary succeed in developing a proper, layered application? No, she didn’t, although she certainly had the best of intentions. She created three Visual Studio projects that correspond to the three layers in the planned architecture. To the casual observer, this looks like the coveted layered architecture, but, as you’ll see, the code is tightly coupled.

Visual Studio makes it easy and natural to work with solutions and projects in this way. If you need functionality from a different library, you can easily add a reference to it and write code that creates new instances of the types defined in the other libraries. Every time you add a reference, though, you take on a Dependency.

2.2.1 Evaluating the dependency graph

When working with solutions in Visual Studio, it’s easy to lose track of the important Dependencies. This is because Visual Studio displays them together with all the other project references that may point to assemblies in the .NET Base Class Library (BCL). To understand how the modules in Mary’s application relate to each other, we can draw a graph of the dependencies (see figure 2.6).

02-06.eps

Figure 2.6 The dependency graph for Mary’s application, showing how the modules depend on each other. The arrows point towards a module’s dependency.

The most remarkable insight to be gained from figure 2.6 is that the UI layer depends on both domain and data access layers. It seems as though the UI could bypass the domain layer in certain cases. This requires further investigation.

2.2.2 Evaluating composability

A major goal of building a three-layer application is to separate concerns. We’d like to separate our domain model from the data access and UI layers so that none of these concerns pollute the domain model. In large applications, it’s essential to be able to work with each area of the application in isolation. To evaluate Mary’s implementation, we can ask a simple question: Is it possible to use each module in isolation?

In theory, we should be able to compose modules any way we like. We may need to write new modules to bind existing modules together in new and unanticipated ways, but, ideally, we should be able to do so without having to modify the existing modules. Can we use the modules in Mary’s application in new and exciting ways? Let’s look at some likely scenarios.

Building a new UI

If Mary’s application becomes a success, the project stakeholders would like her to develop a rich client version in Windows Presentation Foundation (WPF). Is this possible to do while reusing the domain and data access layers?

When we examine the dependency graph in figure 2.6, we can quickly ascertain that no modules are depending on the web UI, so it’s possible to remove it and replace it with a WPF UI. Creating a rich client based on WPF is a new application that shares most of its implementation with the original web application. Figure 2.7 illustrates how a WPF application would need to take the same dependencies as the web application. The original web application can remain unchanged.

02-07.eps

Figure 2.7 Replacing a web UI with a WPF UI is possible because no module depends on the web UI. The dashed box signals the part that we want to replace.

Replacing the UI layer is certainly possible with Mary’s implementation. Let’s examine another interesting decomposition.

Building a new data access layer

Mary’s market analysts figure out that, to optimize profits, her application should be available as a cloud application hosted on Microsoft Azure. In Azure, data can be stored in the highly scalable Azure Table Storage Service. This storage mechanism is based on flexible data containers that contain unconstrained data. The service enforces no particular database schema, and there’s no referential integrity.

Although the most common data access technology on .NET is based on ADO.NET Data Services, the protocol used to communicate with the Table Storage Service is HTTP. This type of database is sometimes known as a key-value database, and it’s a different beast than a relational database accessed through Entity Framework Core.

To enable the e-commerce application as a cloud application, the data access layer must be replaced with a module that uses the Table Storage Service. Is this possible?

From the dependency graph in figure 2.6, we already know that both the UI and domain layers depend on the Entity Framework–based data access layer. If we try to remove the data access layer, the solution will no longer compile without refactoring all other projects because a required Dependency is missing. In a big application with dozens of modules, we could also try to remove the modules that don’t compile to see what would be left. In the case of Mary’s application, it’s evident that we’d have to remove all modules, leaving nothing behind, as figure 2.8 shows.

02-08.eps

Figure 2.8 An attempt to replace the relational data access layer

Although it would be possible to develop an Azure Table data access layer that mimics the API exposed by the original data access layer, there’s no way we could apply that to the application without touching other parts of the application. The application isn’t nearly as composable as the project stakeholders would have liked. Enabling the profit-maximizing cloud abilities requires a major rewrite of the application because none of the existing modules can be reused.

Evaluating other combinations

We could analyze the application for other combinations of modules, but this would be a moot point because we already know that it fails to support an important scenario. Besides, not all combinations make sense.

For instance, we could ask whether it would be possible to replace the domain model with a different implementation. But, in most cases, this would be an odd question to ask because the domain model encapsulates the heart of the application. Without the domain model, most applications have no reason to exist.

2.3 Analysis of missing composability

Why did Mary’s implementation fail to achieve the desired degree of composability? Is it because the UI has a direct dependency on the data access layer? Let’s examine this possibility in greater detail.

2.3.1 Dependency graph analysis

Why does the UI depend on the data access library? The culprit is this domain model’s method signature:

02-11_hedgehog.eps

The GetFeaturedProducts method of the ProductService class returns a sequence of products, but the Product class is defined in the data access layer. Any client consuming the GetFeaturedProducts method must reference the data access layer to be able to compile. It’s possible to change the signature of the method to return a type defined within the domain model. It’d also be more correct, but it doesn’t solve the problem.

02-09.eps

Figure 2.9 Dependency graph of the hypothetical situation where the UI’s dependency on the data access layer is removed

Let’s assume that we break the dependency between the UI and data access library. The modified dependency graph would now look like figure 2.9.

Would such a change enable Mary to replace the relational data access layer with one that encapsulates access to the Azure Table service? Unfortunately, no, because the domain layer still depends on the data access layer. The UI, in turn, still depends on the domain model. If we try to remove the original data access layer, there’d be nothing left of the application. The root cause of the problem lies somewhere else.

2.3.2 Data access interface analysis

The domain model depends on the data access layer because the entire data model is defined there. Using Entity Framework to implement a data access layer may be a reasonable decision. But, from the perspective of loose coupling, consuming it directly in the domain model isn’t.

The offending code can be found spread out in the ProductService class. The constructor creates a new instance of the CommerceContext class and assigns it to a private member variable:

this.dbContext = new CommerceContext();

This tightly couples the ProductService class to the data access layer. There’s no reasonable way you can Intercept this piece of code and replace it with something else. The reference to the data access layer is hard-coded into the ProductService class!

The implementation of the GetFeaturedProducts method uses CommerceContext to pull Product objects from the database:

var featuredProducts =
    from product in this.dbContext.Products
    where product.IsFeatured
    select product;

The reference to CommerceContext within GetFeaturedProducts reinforces the hard-coded dependency, but, at this point, the damage is already done. What we need is a better way to compose modules without such tight coupling. If you look back at the benefits of DI as discussed in chapter 1, you’ll see that Mary’s application fails to have the following:

  • Late binding — Because the domain layer is tightly coupled with the data access layer, it becomes impossible to deploy two versions of the same application, where one connects to a local SQL Server database and the other is hosted on Microsoft Azure using Azure Table Storage. In other words, it’s impossible to load the correct data access layer using late binding.
  • Extensibility — Because all classes in the application are tightly coupled to one another, it becomes costly to plug in Cross-Cutting Concerns like the security feature in chapter 1. Doing so requires many classes in the system to be changed. This tightly coupled design is, therefore, not particularly extensible.
  • Maintainability — Not only would adding Cross-Cutting Concerns require sweeping changes throughout the application, but every newly added Cross-Cutting Concern would likely make each class touched even more complex. Every addition would make a class harder to read. This means that the application isn’t as maintainable as Mary would like.
  • Parallel development — If we stick with the previous example of applying Cross-Cutting Concerns, it’s quite easy to understand that having to make sweeping changes throughout your code base hinders the ability to work with multiple developers in parallel on a single application. Like us, you’ve likely dealt with painful merge conflicts in the past when committing your work to a version control system. A well-designed, loosely coupled system will, among other things, reduce the amount of merge conflicts that you’ll have. When more developers start working on Mary’s application, it’ll become harder and harder to work effectively without stepping on each other’s toes.
  • Testability — We already established that swapping out the data access layer is currently impossible. Testing code without a database, however, is a prerequisite for doing unit testing. But even with integration testing, Mary will likely need some parts of the code to be swapped out, and the current design makes this hard. Mary’s application is, therefore, not Testable.

At this point, you may ask yourself what the desired dependency graph should look like. For the highest degree of reuse, the lowest amount of dependencies is desirable. On the other hand, the application would become rather useless if there were no dependencies at all.

Which dependencies you need and in what direction they should point depends on the requirements. But because we’ve already established that we have no intention of replacing the domain layer with a completely different implementation, it’s safe to assume that other layers can safely depend on it. Figure 2.10 contains a big spoiler for the loosely coupled application you’ll write in the next chapter, but it does show the desired dependency graph.

02-10.eps

Figure 2.10 Dependency graph of the desired situation

The figure shows how we inverted the dependency between the domain and data access layers. We’ll go into more detail on how to do this in the next chapter.

2.3.3 Miscellaneous other issues

We’d like to point out a few other issues with Mary’s code that ought to be addressed.

  • Most of the domain model seems to be implemented in the data access layer. Whereas it’s a technical problem that the domain layer references the data access layer, it’s a conceptual problem that the data access layer defines such a class as the Product class. A public Product class belongs in the domain model.
  • On Jens’ advice, Mary decided to implement in the UI the code that determines whether a user is a preferred customer. But how a customer is identified as a preferred customer is a piece of business logic, so it should be implemented in the domain model. Jens’ argument about Separation of Concerns and the Single Responsibility Principle is no excuse for putting code in the wrong place. Following the Single Responsibility Principle within a single library is entirely possible — that’s the expected approach.
  • Mary loaded the connection string from the configuration file from within theCommerceContextclass (shown in listing 2.2). From the perspective of its consumers, the dependency on this configuration value is completely hidden. As we alluded to when discussing listing 2.2, this implicitness contains a trap.

    Although the ability to configure a compiled application is important, only the finished application should rely on configuration files. It’s more flexible for reusable libraries to be imperatively configurable by their callers, instead of reading configuration files themselves. In the end, the ultimate caller is the application’s entry point. At that point, all relevant configuration data can be read from a configuration file directly at startup and fed to the underlying libraries as needed. We want the configuration that CommerceContext requires to be explicit.

  • The view (as shown in listing 2.5) seems to contain too much functionality. It performs casts and specific string formatting. Such functionality should be moved to the underlying model.

2.4 Conclusion

It’s surprisingly easy to write tightly coupled code. Even when Mary set out with the express intent of writing a three-layer application, it turned into a largely monolithic piece of Spaghetti Code.5  (When we’re talking about layering, we call this Lasagna.)

One of the many reasons that it’s so easy to write tightly coupled code is that both the language features and our tools already pull us in that direction. If you need a new instance of an object, you can use the new keyword. If you don’t have a reference to the required assembly, Visual Studio makes it easy to add. But every time you use the new keyword, you introduce a tight coupling. As discussed in chapter 1, not all tight coupling is bad, but you should strive to prevent tight coupling to Volatile Dependencies.

By now you should begin to understand what it is that makes tightly coupled code so problematic, but we’ve yet to show you how to fix these problems. In the next chapter, we’ll show you a more composable way of building an application with the same features as the one Mary built. We’ll also address those other issues discussed in section 2.3.3 at the same time.

Summary

  • Complex software must address lots of different concerns, such as security, diagnostics, operations, performance, and extensibility.
  • Loose coupling encourages you to address all application concerns in isolation, but ultimately you must still compose this complex set of concerns.
  • It’s easy to create tightly coupled code. Although not all tight coupling is bad, tight coupling to Volatile Dependencies is and should be avoided.
  • In Mary’s application, because the domain layer depended on the data access layer, there was no way to replace the data access layer with a different one. The tight coupling in her application caused Mary to lose the benefits that loose coupling provides: late binding, extensibility, maintainability, Testability, and parallel development.
  • Only the finished application should rely on configuration files. Other parts of the application shouldn’t request values from a configuration file, but should instead be configurable by their callers.
  • The Single Responsibility Principle states that each class should only have one reason to change.
  • The Single Responsibility Principle can be viewed from the perspective of cohesion. Cohesion is defined as the functional relatedness of the elements of a class or module. The lower the amount of relatedness, the lower the cohesion; and the lower the cohesion, the greater the chance a class violates the Single Responsibility Principle.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.228.138