Software design is what we call the deliverable, design, the noun, and what we call the process to make that design, design, the verb, is the creative process of transforming the problem into a solution. In our case, transforming a requirement specification into a detailed description of the
software that’s code-ready. The noun then is the documented description of that solution and the constraints and explanations used to arrive at it.
In the V-model of software development, software design comes into the process at the fourth
stage, after architecture and before implementation. It sits between the enterprise-level decisions in the subsystem designing and the development effort.
Architecture is primarily focused on overarching, cross-cutting concerns for our system, especially in the context of the entire enterprise. Large scale decisions like, should we build or buy the software from another company? How is security going to be handled, by the server or by the application? Lots of enterprise and management-focused decisions go into this too like apportioning resources and personnel, deciding if the current staff and hardware can handle the project itself, and what it’s going to cost to get us there. Securing the internal funding for such endeavors is often looked at as an architectural concern.
The first thing we do is get a good problem understanding when it comes to design. Most of this should come from your requirements and specification documents. TMTOWTDI, there’s more than one way to do it. It’s a pretty common acronym in technology because it’s so often true. Don’t be tunnel-visioned into any large-scale solution as always the only way to go about solving the problem. There is almost always another way to reach the same singular goal, so consider
multiple alternatives before deciding definitively which one to pursue.
Software design is all about designing a solution, creating the deliverables and documentation necessary to allow the developing team to build something that meets the needs of the user or the client. The best people to do that is the designing team. This is a crucial step that moves from our natural language understanding to code-ready solutions.
When we talk about modularity, we’re primarily talking about these four things. Coupling, and cohesion are measures of how well modules work together and how well each individual module meets a certain single well-defined task and they tend to go together, so we’ll talk about them separately. Information hiding describes our ability to abstract away information and knowledge in a way that allows us to complete complex work in parallel without having to know all the implementation details concerning how the task will be completed eventually. And then,
data encapsulation refers to the idea that we can contain constructs and concepts within a module allowing us to much more easily understand and manipulate the concept when we’re looking at it in relative isolation.
We have no choice but to break the problem down into smaller parts which we might then be
able to comprehend. To do that properly, we’re going to focus on three concepts. One is Decomposability. Essentially it’s the ancient, possibly Roman, concept of divide and conquer. When the problem is too large and complex to get a proper handle on it, breaking it down into smaller parts until you can solve the smaller part is the way to go. Then you just solve all the smaller parts.
But then we have to put all those smaller parts back together and that’s where composability comes into play. This is often not as simple as one would like. NASA’s Mars Climate Orbiter disintegrated during its mission because of a mistake in units with one module using pound-seconds and the other using Newton-seconds when calculating its thruster’s total
In architecture and design, we follow these six stages. The first three are architectural. The last three, design. After we decide on system architecture, separate behavior responsibility into components, and determine how those components will interact through their interfaces, we set out to design the individual components. Each component is designed in isolation, the benefit of encapsulation and reliance on those interfaces we design. Once each component is fully designed in isolation, any data structures which are inherently complex, important, shared between the classes, or even shared between components, are then designed for efficiency. The same goes for algorithms.
When the algorithm is particularly complex, novel, or important to the successful fulfillment of the components’ required behavior, you might see software designers rather, than the developers, writing pseudo code to ensure that the algorithm is properly built. Software design takes abstract requirements and then you build the detail and until you’re satisfied that you can hand it off and it will be developed properly.
When we say solution abstractions, we essentially mean any documentation of the solution that isn’t technological. Mostly, that means anything that’s not code or hardware. Graphical including mock-ups or wireframes, formal descriptions including unified modeling language or UML diagrams like class diagrams and sequence diagrams, and other descriptive notations should be used to capture your description of the solution that you intend to build or have built for you. What you’re going to do is repeat for all abstractions, subsystems, components, etc. under the entire design and until the entire design is expressed in primitive terms.
So, you’re going to decide things like classes, methods, data types, that kind of thing but not the individual language-specific optimizations that will go into the eventual code. So, you’re going to provide detail, which is implementation-ready but it doesn’t include implementation detail.
Object-oriented thinking involves examining the problems or concepts at hand, breaking them down into component parts, modelling these concepts as objects in your software. Conceptual design uses object-oriented analysis to identify the key objects in the problem and breaks down the problem into manageable pieces.
Technical design uses object-oriented design to further refine the details of the objects, including their attributes and behaviors, so it is clear enough for developers to implement as working software. The goal during software design is to construct and refine “models” of all the objects of the software.
Categories of objects involve:
• entity objects, where initial focus during the design is placed in the problem space
• control objects that receive events and co-ordinate actions as the process moves to the solution space
• boundary objects that connect outside services to your system, as the process moves towards the solution space
Software models are often expressed in a visual notation, called Unified Modelling Language (UML). Object-oriented modelling has different kinds of models or UML diagrams that can be used to focus on different software issues. For example, a structural model might be used to describe what objects do and how they relate. This is analogous to a scale model of a building, which is used in architecture.
However, to create an object-oriented program, you must examine the major design principles of such programs. Four of these major principles are: abstraction, encapsulation, decomposition, and generalization.
There are three types of relationships in decomposition, which define the interaction between the whole and the parts: Association, aggregation, composition. However, to guide technical design UML class diagram, also known as simply a class diagram. allow for easier conversion to classes for coding and implementation.
The metrics often used to evaluate design complexities are coupling and cohesion.
Coupling: When the requirements are changed, and they will be, maybe halfway through our process, we don’t want those changes to have massive impacts across the entirety of our system. When you produce effective low coupling, changes in one module shouldn’t affect the other modules, or should do so as minimally as possible. In order to evaluate the coupling of a module, the metrics to consider are: degree (number of connections between the module and others.) ease, and flexibility (indicates how interchangeable the other modules are for this module.)
The three types of coupling are tight coupling ( Content, common and external), Medium ( control and data structure) and Loose ( data and message).
Both content and common coupling occur when two modules rely on the same underlying information. Content coupling happens when module A directly relies on the local data members of module B rather than relying on some access or a method. While common coupling happens when module A and module B both rely on some global data or global variable.
External coupling is a reliance on an externally imposed format, protocol, or interface. In some cases, this can’t be avoided, but it does represent tight coupling, which means that changes here could affect a large number of modules, which is probably not ideal. You might consider, for example, creating some abstraction to deal with the externally imposed format, allowing the various modules to maintain their own format, and delegating the format to the external but into a single entity, depending on whether or not the external format or the internal data will change more often.
Control coupling happens when a module can control the logical flow of another by passing in information on what to do or the order in which to do it, a what-to-do flag. Changing the process may then necessitate changes to any module which controlled that part of the process. That’s not necessarily good. Data structure coupling occurs when two modules rely on the same composite data structure, especially if the parts the modules rely on are distinct. Changing the data structure could adversely affect the other module, even when the parts of the data structure that were changed aren’t necessarily those that were relied on by that other module.
And finally, we have the loosest forms of coupling. Data coupling is when only parameters are shared. This includes elementary pieces of data like when you pass an integer to a function to
compute the square root. Message coupling is then the loosest type of coupling. It’s primarily achieved through state decentralization, and component communication is only accomplished either through parameters or message passing.
Cohesion focuses on complexity within a module and represents the clarity of the responsibilities of a module. Cohesion is really how well everything within a module fits together, how well it works towards a singular purpose. Cohesion can be weak(coincidental, temporal, procedural, logical), medium( communicational, procedural), strong(object, functional)
Coincidental cohesion is effectively the idea that parts of the module are together just because they are. They are in the same file. Temporal cohesion means that the code is activated at the same time, but that’s it. That’s really the only connection. Procedural cohesion is similarly time-based and not very strong cohesion. Just because one comes after the other doesn’t really tie them together, not necessarily. Logical association then is the idea that components which perform similar functions are grouped. We’re getting less weak, but it’s still not good enough. The idea here is that at some level the components do similar, but separate or parallel things. That’s not a good reason to combine them in a module. They are considered separate
Communicational cohesion means that all elements of the component operate on the same input or produce the same output. This is more than just doing a similar function. It’s producing identical types of output or working from a singular input. And then sequential cohesion is
the stronger form of procedural cohesion. Instead of merely following the other in time, sequential cohesion is achieved when one part of the component is the input to another part of the component. It’s a direct handoff and a cohesive identity.
Finally, we get to the strongest forms of cohesion, your goal as a designer. In object cohesion, we see that each operation in a module is provided to allow the object attributes to be modified or inspected. Every single operation in the module. Each part is specifically designed for purpose within the object itself, that’s that object cohesion. And then functional cohesion goes above and beyond sequential cohesion to assure that every part of the component is necessary for the execution of a single well-defined function or behavior. So it’s not just input to output, it’s everything together is functionally cohesive.
Conceptual integrity is a concept related to creating consistent software. There are multiple ways to achieve conceptual integrity. These include communication, code reviews, using certain design principles and programming constructs, having a well-defined design or architecture underlying the software, unifying concepts, having a small core group that accepts each commit to the code base.
Some good practices to foster communication include agile development practices like daily stand-up meetings and sprint retrospectives.
Using certain design principles and programming constructs helps maintain conceptual integrity. Notably, Java interfaces are a construct that can accomplish this. An interface defines a type with a set of expected behaviors. Implementing classes of that interface will have these behaviors in common. This creates consistency in the software, and increases conceptual integrity.
A Java interface also denotes a type, but an interface only declares method signatures, with no constructors, attributes, or method bodies. It specifies the expected behaviours in the method signatures, but it does not provide any implementation details. Like abstract classes, which are classes that cannot be instantiated, interfaces are a means in which you can achieve polymorphism. In object-oriented languages, polymorphism is when two classes have the same description of a behaviour, but the implementations of that behaviour may be different.
Philippe Kruchten’s 4+1 View Model
Multiple perspectives are necessary to capture the complete behavior and development of a software system. Together, logical, process, development, and physical views, along with scenarios form Philippe Kruchten’s 4+1 View Model. The logical view, which focuses on the functional requirements of a system, usually involves the objects of the system. From these objects, a UML class diagram can be created to illustrate the logical view.
The process view focuses on achieving non-functional requirements. These are the requirements that specify the desired qualities for the system, which include quality attributes such as performance and availability. Some of the most effective UML diagrams related to the process view of a system are the activity diagram and the sequence diagram. The sequence diagram shows how objects interact with one another, which involves how methods are executed and in what order.
UML sequence diagrams are another important technique in software design. In simple terms, a sequence diagram is like a map of conversations between different people, with the messages sent from person to person-outlined. UML state diagrams are a technique used to describe how systems behave and respond. They follow the states of a system or a single object and show changes between the states as a series of events occur in the system.
The development view describes the hierarchical software structure. It also considers elements such as programming language, libraries, and toolsets.
Physical View The physical view handles how elements in the logical, process, and development views must be mapped to different nodes or hardware for running the system.
Scenarios align with the use cases or user tasks of a system and show how the four other views work together. For each scenario, there is a script that describes the sequence of interactions between objects and processes.
UML component diagrams are concerned with the components of a system. Components are the independent, encapsulated units within a system. Each component provides an interface for other components to interact with it. Component diagrams are used to visualize how a system’s pieces interact and what relationships they have among them. A UML activity diagram allows the representation of the control flow from activity to another in a software system. It captures the dynamic behaviour of the system and allows the mapping of branching into alternative flows.