Object-Oriented Software Design Principles (IV)-Package Design Principles

Source: Internet
Author: User

With the applicationProgramTo increase the scale and complexity, you need to organize them at a higher level. Class is a very convenient organizational unit for small applications, but for large applications, if only class is used as a unique organizational unit, it will appear fine-grained. Therefore, it is necessary to "something" that is larger than the class to assist in the organization of large applications. This "thing" is a package ).

This section describes six principles. The first three principles focus on the cohesion of the package. These principles can guide us in how to divide classes into packages. The next three principles focus on package coupling, which helps us determine the relationship between packages.

 

In the concept of UML, a package can be used as a container that contains a group of classes. By organizing classes into packages, we can understand the design at a higher level of abstraction. We can also manage software development and release through packages. The purpose is to divide the classes in the application according to some principles, and then allocate these classes to the package.

 

However, classes often have dependency relationships with other classes. These dependency relationships often span the packet boundary. Therefore, dependencies between packages are also generated. The dependency between packages shows the application's high-level organizational structure. We should manage these relationships.

 

1. granularity: Package cohesion principle

The three package cohesion principles described here can help developers decide how to divide classes into packages. These principles depend on the fact that at least some classes already exist and the relationships between them have been determined. Therefore, these principles divide classes based on the "bottom-up" viewpoint.

1.1 reuse release Equivalence Principle (REP)

The granularity of reuse is the granularity of Release: software in a package can be reused or not.

 

When you reuse a class library, what do you expect from the author of this class library? Of course you want to get a good document. You can work on it.CodeInterface with clear specifications. However, you have other expectations.

First, you want the author of the Code to maintain the code for you. Only in this way can you spend time reusing the code. After all, if you need to maintain the code yourself, it will take a lot of time for you to design a smaller but better package.

Second, you want the author of the Code to notify you in advance when you plan to make any changes to the Code interfaces and functions. However, it is not enough to notify you. The author of the Code must respect your power to refuse to use any new version. Otherwise, a new version may be released when you are at a critical moment in the development progress. Or he makes changes to the Code, and then he simply cannot be compatible with your system.

 

In either case, if you decide not to accept the new version, the author must ensure that the old version you are using will continue to provide support for a period of time. This may be only three months, or one year long. You two must negotiate on these things. However, he cannot disconnect from you and refuse to provide support to you. If he doesn't agree to provide support for the older version you are using, you should carefully consider whether you are willing to endure the volatile changes of the other party and continue to use his code.

 

This problem is mainly caused by administrative issues. If someone else wants to reuse the code, administrative and support work is required. However, these administrative issues have a profound impact on the structure of the software package. To provide the necessary assurance for the reusable, the author of the Code must organize their software into a reusable package and track the packages through the version number.

 

Rep pointed out that the granule of reuse of a package can be as large as the granule of release. We say that everything that is reused must be released and tracked at the same time. It is unrealistic to simply write a class and then claim that it is reusable. Only after a tracking system is established to provide the required change notification, security, and support to potential users can the system be reused.

 

Rep gave us the first tip about how to divide the design into packages. Since the reusability must be package-based, reusable packages must contain reusable classes. Therefore, at least some packages should have a group of reusable classes.

 

Administrative binding will affect the division of software, which seems disturbing, but software is not a pure mathematical entity that can be organized based on pure mathematical rules. Software is a product of a person's intellectual activities. Software is created and used by people. If we want to reuse the software, it must be divided in a way that people think is convenient.

 

So what have we learned about the internal structure of the package? We must consider the contents of the package from the perspective of potential reusable users. If the software in a package is used for reuse, it cannot contain code that is not designed for reuse purposes. The code in a package is either reusable or reusable.

 

Reusability is not the only criterion. We also need to consider people who reuse the software. Of course, a container class library is reusable, and a financial framework is reusable. However, we do not want to put them in the same package. Many people who want to reuse container class libraries may not be interested in financial frameworks at all. Therefore, we hope that all classes in a package can be reused for users of the same class. We do not want a user to find that some of the classes in the package are what he needs, and others are useless to him.

1.2 common Reuse Principle (CRP)

All classes in a package should be reused together. If a class in the package is reused, all classes in the package are reused.

 

This principle helps us decide which classes should be placed in the same package. It specifies that classes that tend to be reused together should belong to the same package.

Classes are rarely reused in isolation. In general, reusable classes need to collaborate with other classes that are part of the reusable abstraction. CRP specifies that these classes should belong to the same package. In such a package, we will see a lot of dependencies between classes.

A simple example is the container class and the iterator class associated with it. These classes are closely coupled with each other and must be reused together. So they should be in the same package

However, CRP tells us not only what classes should be put into a package together. It also tells us what classes should not be put into the same package. When one package uses another package, there is a dependency between them. Maybe a package only uses one class in another package. However, it will not weaken the dependency between the two packages. The user package is still dependent on the used package. The user package must be re-verified and re-released whenever the used package is released. This is required even if the reason for publishing only changes the class that the user package does not care about at all.

 

In addition, packages are often displayed in the form of physical representations such as shared libraries, DLL, and jar. If the used package is released as a jar, the code for using this package depends on the entire jar. Any modifications to the jar -- a new version of the jar will be released even if the modified class is irrelevant to the user code. The new jar should also be re-released, and the code using this jar should also be re-verified.

 

Therefore, I want to be sure that when I depend on a package, I will depend on every class in that package. In other words, I want to be sure that all classes in a package cannot be separated. It is impossible to rely only on a part of the package. Otherwise, I will perform unnecessary re-verification and re-release, and a considerable amount of effort will be wasted.

 

Therefore,CRP tells us more about what classes should not be put together. CRP specifies that classes that are not closely related to each other should not be placed in the same package.

1.3 Common blocking principle (CCP)

All classes in the package should be closed for changes to the same class nature. If a change affects a package, it will affect all classes in the package, without affecting other packages.

 

This is a single principle of responsibility (SRP) for the re-provision of the package. Just as a class specified by SRP should not contain multiple reasons for change,This principle stipulates that a package should not contain multiple reasons for change.

 

In most applications, maintainability is more important than reusability. If the code in an application must be changed, we would rather concentrate the changes in one package instead of distributed in multiple packages. If the changes are concentrated in a single package, we only need to release the changed package. Other packages that do not depend on the changed package do not need to be re-verified or re-released.

 

The CCP encourages us to gather all classes that may change for the same reason in the same place. If there is a very close binding relationship between the two classes, both physically and conceptually, they will always change together, so they should belong to the same package, this reduces the workload of software release, re-verification, and re-release.

 

This principle is closely related to the open-closed principle (OCP. In this principle, the word "closed" has the same meaning as that in OCP. OCP stipulates that classes should be closed for modifications and extensions should be open. However, as we have learned, 100% of the closures are impossible. Policies should be closed.The system we designed should be closed to the most common changes we have ever experienced.

 

The CCP enhances the preceding content by organizing classes that are open to certain types of changes to the same package. Therefore, when one of the requirements changes, the changes are likely to be limited to the minimum number of packages.

1.4 package cohesion Summary

In the past, our understanding of cohesion was far simpler than the above three principles. We are used to think that cohesion only refers to the execution of one module and only one function. However, these three principles about cohesion describe more about cohesion changes. When selecting a class to be co-organized to the package, you must consider the inverse force between reusability and development ability. Balancing these strengths and application needs is not a simple task. In addition, this balance is almost always dynamic. That is to say, it seems that the appropriate division may not be appropriate until next year. Therefore,When the focus of a Project changes from development to reusability, the composition of packages may change and evolve over time.

 

 

2. Stability: Package coupling principle

The following three principles are used to process the relationship between packages. Here, we will once again encounter the tension between developability and logical design ). The technical and administrative forces will affect the organizational structure of the package, and this force is still changeable.

2.1 No-ring dependency principle (ADP)

In the dependency graph of the package, loops are not allowed.

 

Consider the package diagram in Figure 2.6.2.1-1. The figure shows a very typical package structure of an application. Compared with the intent of this example, the functions of this application are not important. What is important is the dependency relationship structure of the package. Note that this mechanism is a directed graph (directed graph ). The package is a node, and the dependency is a directed edge ).

 

Figure 2.6.2.1-1 The package structure is a directed acyclic graph.

 

Now, pay attention to another thing. No matter which package starts, the package cannot be rolled back along the dependency. This structure does not contain loops. It is a directed acyclic graph (DAG ).

 

When the team in charge of mydialogs releases a new version of the package, it is easy to find the affected package: you only need to direct the dependency to the search. Therefore, both mytasks and myapplication are affected. Developers working on these two packages need to decide when to integrate with the new version of mydialogs.

 

Note that when mydialogs is released, many other packages in the system are not affected at all. They do not know mydialogs and do not care when mydialogs is changed. This is good. This means that the release of mydialogs has a relatively small impact.

 

When developers working on the mydialogs package want to run the test of this package, they only need to compile and link their mydialogs version with the version of the Windows package currently in use. It does not involve any other packages in the system. This is good. This means that developers working on mydialogs can build a test with only a small amount of work, and there are not many variables to consider.

 

The entire system is released from the bottom up. First, compile and test the released Windows package. Followed by messagewindow and mydiaolgs. After them are tasks, then taskwindow and database. Next is mytasks, and the last is myapplication. This process is very clear and easy to handle. We know how to build a system because we understand the dependencies between various parts of the system.

2.1.1 impact on the central part of the package dependency Diagram

If a new requirement forces us to change a class in mydialogs to use a class in myapplication. This generates a dependency ring, as shown in 2.6.2.1.1-1:

 

Figure 2.6.2.1.1-1 package with a dependency Ring

 

This dependency ring may cause some direct consequences. For example, developers working on the mytasks package know that to publish the mytasks package, they must be compatible with tasks, mydialogs, database, and windows. However, because of the dependency, they must be compatible with myapplication, taskwindow, and messagewindow. That is to say, mytasks depends on all other packages in the system. This makes mytasks very difficult to publish. Mydialogs has the same problem. In fact, this dependency forces myapplication, mytasks, and mydialogs to be released at the same time. They have actually become the same big package. As a result, all developers working on these packages should be completely consistent with each other's release actions, because they must all use identical versions of each other.

 

This is only part of the problem. Consider what will happen when you want to test the mydialogs package. We must link all the other packages in the system, including the database package. This means that a complete build is required only to test mydialogs. This is intolerable.

 

If you want to know why you have to link to so many different libraries and so many other people's code, you just need to run a simple unit test of a class, perhaps this is because the dependency graph has a ring. This ring makes it very difficult to isolate modules. Unit Testing and publishing become very difficult and error-prone. Moreover, in C ++, the Compilation Time will increase with the number of modules in a geometric series.

 

In addition, if the dependency graph contains loops, it is difficult to determine the order of packet construction. In fact, there may be no proper order. For languages like Java that want to read their declarations from compiled binary files, this can cause some very annoying problems.

2.1.2 release the dependency Ring

In any case, the dependency loops between packages can be removed and the dependency graph can be restored to a Dag. There are two main methods:

(1) Use the Dependency inversion principle (DIP ). For example, in Figure 2.6.2.1.2-1, you can create an abstract base class with interfaces required by mydialogs. Then, put the base class of the image into mydialogs, and make the class in myapplication inherit from it. This leads to the inversion of the dependency between mydialogs and myapplication, thus removing the dependency ring. (See Figure 2.6.2.1.2-1)

 

Figure 2.6.2.1.2-1 use Dependency inversion to remove dependency Loops

 

(2) create a new package on which both mydialogs and myapplication depend. Move the classes that both mydialogs and myapplication depend on to this new package. (See Figure 2.6.2.1.2-2)

Figure 2.6.2.1.2-2 use the new package to remove the dependency Ring

 

The 2nd solution means that the package structure is unstable before the demand changes. In fact, as the application grows, the dependency structure of the package will be jitters and grow. Therefore, the central dependency structure must always be monitored. If a ring exists, you must remove it in some way. Sometimes this means creating a new package causes the dependency structure to grow.

2.1.3 top-down Design

Now, we can draw an inevitable conclusion:The structure of the package cannot be designed from top to bottom.This means that the package structure is not one of the first considerations when designing the system. In fact, the package structure should evolve gradually as the system grows and changes.

 

Maybe you think this is against intuition. We already think that large-granularity decomposition such as package is also a high-level functional decomposition. When we see a large-granularity group like the package dependency structure, we will feel that the package should depict the system functions in some way. However, this may not be an attribute of the package dependency graph.

 

In fact, There is almost no relationship between the dependency graph of the package and the function of describing the application. On the contrary, they are mappings of application build-ability.This is why they are not designed at the beginning of the project. No software can be built at the beginning of the project, so you do not need to build a map. However, as more classes are accumulated in the implementation and design stages, the demand for dependency management continues to grow. In addition, we also want to keep the localization of changes as much as possible, so we begin to focus on SRP and CCP and put classes that may change together.

 

As applications grow, we are beginning to focus on creating reusable elements. Therefore, CRP was used to guide the combination of packages. Finally, when the ring appears, ADP will be used, so that the dependency graph of the package will be jittery and grow.

 

If you try to design the dependency structure of the package before designing any class, it is likely to suffer a fiasco. We do not know much about common closures and are not aware of any reusable elements. Therefore, we will almost certainly create packages that generate dependency loops. So,

2.2 stable dependency principle (SDP)

Dependency in a stable direction.

 

The design cannot be completely fixed. To make the design maintainable, some degree of variability is necessary. We achieve this goal by following the common blocking principle (CCP. With this principle, you can create packages that are sensitive to certain change types. These packages are designed to be variable. We expect their changes.

 

For any package, if you expect it to be variable, you should not make a variable package dependent on it! Otherwise, it is difficult to change the variable package.

 

You have designed an easy-to-Change Package. Other people only need to create a dependency on it to make it hard to change. This is an abnormal feature of the software. Without changing any line of code in your module, it suddenly becomes difficult to change. By following SDP, we can ensure that those modules that are intended to be easy to change are not depended on by those modules that are more difficult to change than they are.

2.2.1 not all packages should be stable

If a system is of maximum stability in all packages, the system cannot be changed. This is not the expected situation. In fact, we hope that some packages in the designed package structure are unstable while others are stable. Figure 2.6.2.2.1-1 shows the ideal configuration of a system with three packages.

 

 

Figure 2.6.2.2.1-1 ideal package Configuration

 

The changeable package is located at the top and depends on the stable package at the bottom.Placing an unstable package on the top of the graph is a useful convention, because any up arrow means a violation of SDP.

 

Figure 2.6.2.2.1-2 shows how SDP is violated. We intend to make the flexvisible package easy to change. We want flexible to be unstable. However, some developers who work on the stable package have created a dependency on flexable. This violates SDP. As a result, flexible is no longer easy to change. Changes to flexible will force us to deal with the impact of the changes on stable and all dependent persons.

 

 

Figure 2.6.2.2.1-2 violates SDP

 

To fix this problem, we must remove the dependency of stable on flexible in some way. Why does this dependency exist? Assume that one of the classes in flexible is used by Class U in another stable (see Figure 2.6.2.2.1-3)

 

 

Figure 2.6.2.2.1-3 reasons for poor dependency

 

You can use dip to fix this problem. We create an interface class IU and place it in the package uinterface. We make sure that all methods to be used by u are declared in IU. Next, let C inherit from this interface (see Figure 2.6.2.2.1-4 ). This removes the dependency of stable on flexible and causes both packages to depend on uinterface. The uinterface is very stable, while flexible still maintains the necessary instability.

 

Figure 2.6.2.2.1-4 Use dip to correct stability violations

2.2.2 where should I place a high-level design?

Some software in the system should not change frequently. The software represents the high-level architecture and design decisions of the system. We hope these architecture decisions are stable. ThereforeShould put the software of the high-level design of the encapsulation system into a stable package. The unstable package should only contain software that is likely to change.

 

However, if the high-level design is put into a stable package, it will reflect the high-level designSource codeIt will be difficult to change. This will make the design inflexible. How can we make a package with the highest stability flexible enough to withstand changes? You can find the answer in OCP. The OCP principle tells us that classes that are flexible enough to be extended without modification exist and are expected. Which types comply with the OCP principles? -- Abstract class.

1.1.2.3 stable abstraction principle (SAP)

The abstraction level of the package should be consistent with its stability.

 

This principle associates the stability of the package with abstraction. It specifiesA stable package should also be abstractSo that its stability will not make it unable to expand. On the other hand, it stipulates that,An unstable package should be specificBecause of its instability, the specific internal code is easy to change.

 

Therefore, if a package is stable, it should also contain some abstract classes so that it can be expanded. Scalable stable packages are flexible and do not limit the design.

 

SAP and SDP are combined to form a packet-specific dip principle.. This is accurate, because the SDP requires that the dependency should be in a stable direction, while sap stipulates that stability means abstraction. Therefore, the dependency should be in the abstract direction.

 

However, dip is a processing class principle. Class does not have the concept of the shades of gray. A class is either abstract or not. The combination of SDP and SAP processes packages and allows a package to be partially abstract and partially stable.

 

Next chapter: Object-oriented software design principles (V)-Application Examples

Codeproject

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.