|S||Single responsibility principle||A class/method should have only one responsibility.|
|O||Open/closed principle||An object should be open for extension but closed for modification.|
|L||Liskov substitution principle|| A base type should be replaceable with subtypes in each and every situation.
Is-A inheritance is actually Is-Substitutable-For.
|I||Interface segregation principle|| Use client-specific interfaces instead of one general interface.
By using small interfaces, you don't force clients to implement more than they need. Prefer small, cohesive interfaces to “fat” interfaces.
|D||Dependency inversion principle||Depend upon abstractions such as an interface or an abstract class. This makes the code less coupled to the actual implementation.|
|Sequential Approach||Iterative Approach|
|The requirements are fairly stable.||The requirements are not well understood or you expect them to be unstable.|
|The design is straightforward and fairly well understood.||The design is complex, challenging, or both.|
|The development team is familiar with the applications area.||The development team is unfamiliar with the applications area.|
|The project contains little risk.||The project contains a lot of risk.|
|Long-term predictability is important.||Long-term predictability is not important.|
|The cost of changing requirements, design, and code downstream is likely to be high.||The cost of changing requirements, design, and code downstream is likely to be low.|
The development process helps customers better understand their own needs, and this is a major source of requirements changes. [Curtis, Krasner, and Iscoe 1988; Jones 1998; Wiegners 2003]
Quotes from “Professional C++” by Marc Gregoire:
“Experience and iteration are essential to good abstractions. Truly well-designed interfaces come from years of writing and using other abstractions. The best interface is rarely the first one you put on paper, so keep iterating.”
“Don't be afraid to change the abstraction once coding has begun, even if it means forcing other programmers to adapt. Sometimes you need to evangelize a bit when communicating your design to other programmers. Perhaps the rest of the team didn't see a problem with the previous design or feels that your approach requires too much work on their part. In those situations, be prepared both to defend your work and to incorporate their ideas when appropriate.”
“Iteration is worth mentioning again because it is the most important point. Seek and respond to feedback on your design, change it when necessary, and learn from mistakes.”
An excerpt from “Professional C++” by Marc Gregoire:
Using a library under the GPL (GNU) might require you to make your own product open-source as well. Boost, OpenBSD, CodeGuru, CodeProject, Creative Commons License allow for using the open-source library in a closed-source product.
Open-source libraries are usually written by people in their “free” time. As a good programming citizen, you should try to contribute to open-source projects if you find yourself reaping the benefits of open-source libraries. If you work for a company, you may find resistance to this idea from your management because it does not lead directly to revenue for your company. However, you might be able to convince management that indirect benefits, such as exposure of your company name, and perceived support from your company for the open-source movement, should allow you to pursue this activity.
When you design or write code as an employee of a company, the company, not you, owns the intellectual property rights. It is often illegal to retain copies of your designs or code when you terminate your employment with the company. The same is also true when you are self-employed working for clients.
Characteristics of XP:
Directory structure for a game and a game engine:
Do not compare floats for equality or inequality. Rather, test that the absolute value of the difference is less that a specified small value.
Hashing is the process of turning a key of a data type into an integer. This integer can be used modulo the table size as an index into the table.
Given a key k, we want to generate an integer hash value h using the hash function H, and then find the index i into the table:
h = H(k) i = h mod N
N is the number of slots in the table.
A dictionary data structure is usually implemented as a binary search tree or as a hash table. In a hash table implementation, the values are stored in a fixed-size table, where each slot in the table represents one or more keys. Finding a key-value pair is an O(1) operation in the absence of collisions.
A collision is a situation when two or more keys end up occupying the same slot in the hash table. There are two ways to resolve a collision, each related to a different kind of a hash table:
In an open hash table, collisions are resolved by storing more than one key-value pair at each index, usually as a linked list. This approach does not impose an upper bound on the number of key-value pairs that can be stored. However, it requires memory to be allocated dynamically whenever a new key-value pair is added to the table.
In a closed hash table, collisions are resolved via probing until an empty slot is found. This approach imposes an upper limit on the number of key-value pairs that can be stored. The main benefit is that it uses a fixed amount of memory hence no memory allocations are needed.
Reference: API Design for C++ by Martin Reddy
Q: What is API?
A: An API is a logical interface to a software component that hides the internal details required to implement it. An API is written to solve a particular problem or perform a specific task.
Q: What are the requirements for a good API?
A: API must be well-designed, documented, regression tested, and stable between releases.
Q: What are the benefits of using or writing an APIs?
Q: How APIs model the problem domain?
A: The API should model the problem domain i.e., API should be formulated in terms of high-level concepts that make sense in the problem domain rather than exposing low-level implementation details. A non-programmer should be able to understand the concepts of the API's interface and how it works.
Q: What are the two techniques used to hide implementation details?
A: The two techniques are:
Q: How is physical hiding achieved in C++?
A: Physical hiding means storing internal details in a separate file (.cpp) from the public interface (.h)
Q: How is logical hiding achieved in C++?
A: Logical hiding means using C++ language features of
private to restrict access to internal details.
Q: What are the benefits of using the getter/setter routines, rather than exposing member variables directly?
A: The benefits are:
Q: What level of encapsulation should you use for data members?
A: Data members should always be private, never public or protected. Alan Snyder : “inheritance severely compromises the benefits of encapsulation in object-oriented programming languages”.
Q: In what scenario you might consider exposing member variables?
A: The only plausible argument for exposing member variables is for performance reasons such as in a tight loop that performs large number of operations. However, even in this case the careful use of inlining, combined with a modern optimizing compiler, may completely eradicate the method call overhead.
Q: Should an API be minimally complete?
A: A good API should be minimally complete. This is one of the most important qualities of an AP. You should try to keep your APIs as simple as you can: minimize the number of classes you expose and the number of public members in those classes. This will also make your API easier to understand, easier for your users to keep a mental model of the API in their heads, and easier for you to debug.
Every public element in your API is a promise - a promise that you will support that functionality for the lifetime of the API. The key point is that once you release an API and have clients using it, adding new functionality is easy, but removing functionality is really difficult. When in doubt, leave it out [Bloch 2008; Tulach 2008].
There is a temptation to add extra levels of abstraction or generality to an API because you think it might be useful in the future. You should resist this temptation for the following reasons:
Add Virtual Functions Judiciously
One subtle way that you can expose more functionality that you intended is through inheritance, that is, by making certain member functions virtual:
A class with no virtual functions tends to be more robust and requires less maintenance than one with virtual functions. A rule of thumb: if your API does not call a particular method internally, then that method probably should not be virtual. You should also allow subclassing is situations where the potential subclasses form an “is-a” relationship with the base class.
If you still decide that you want to allow subclassing, remember the following rules:
On one hand, an API should only provide one way to perform one task. This ensures that the API is minimal, focused, consistent, and easy to understand. On the other hand, an API should make simple things easy to do. Clients should not be required to write lots of code to perform basic tasks.
Both these goals can be achieved by writing convenience wrappers according to certain rules. Convenience wrappers are utility routines that encapsulate multiple API calls to provide simpler higher-level operations.
Add convenience APIs as separate modules or libraries that sit on top of your minimal core API.
Q: What does it mean that an API is easy to use?
A: A well-designed API should make simple tasks easy. It should be possible for a client to look at the method signatures of your API and be able to glean how to use it, without any additional documentation. This API quality follows from minimalism: if your API is simple, it should be easy to understand.
Q: What additional artifacts help clients to use your API?
A: Your API should provide:
Q: What does it mean that an API is discoverable?
A: A discoverable API is one where users are able to work out how to use the API on their own, without any accompanying explanation or documentation.
Q: Give an example of misusing an API?
A: A good API should be difficult to misuse. For example, an API may be misused by passing the wrong arguments to a method or passing illegal values to a method.
Q: How can you minimize misuse of your API?
A: You can make your API more difficult to misuse by following the guidelines:
Q: What is static polymorphism?
A: Static polymorphism refers to identifying the common concepts across your classes and using the same conventions to represent these concepts in each class. For example, STL containers do not inherit from a common base class but they follow the same patterns and idioms such as iterators
Q: What does it mean that an API is orthogonal?
A: An orthogonal API means that functions do not have side effects. For example, calling a method that sets a particular property should change only that property and not additionally change other publicly accessible properties. Another interpretation of orthogonal design is that different operations can all be applied to each available data type. For example, STL offers a collection of generic algorithms and iterators that can be used on any container.
Q: What are the two important factors of the orthogonal APIs?
Q: What is RAII (Resource Acquisition Is Initialization)?
A: RAII is a technique related to resource management where resources are chunks of memory, file handles, mutex locks, etc. Consider providing a class to manage resources. Think of resource allocation and deallocation as object construction and destruction.
Q: What are good practices to make an API platform independent?
A: A platform independent API should:
#ifdeflines in its public headers. There are very few cases where API should be different for different platforms.
Q: What is coupling and cohesion?
A: Coupling. A measure of the strength of interconnection between software components, that is, the degree to which each component depends on other components in the system. One way to think of coupling is that given two components, A and B, how much code in B must change if A changes. Cohesion. A measure of how coherent or strongly related the various functions of a single software component are.
Good APIs exhibit loose coupling and high cohesion.
Q: What are the benefits of loose coupling and high cohesion?
A: Loose coupling and high cohesion allows components to be used, understood, and maintained independently of each other.
Q: What are measures to evaluate the degree of coupling between components?
A: Measures to evaluate the degree of coupling between components are:
Q: What is circular dependency?
A: Circular dependency (or a dependency cycle) is a form of tight coupling where two components depend on each other directly or indirectly.
Q: What are techniques to reduce coupling between classes and methods within an API (inter-API coupling)?
A: Techniques to reduce coupling within an API are:
std::uniquewhich are declared outside of container classes.
Q: How your API design decisions affect the cohesion and coupling of your clients' applications (intra-API coupling)?
A: The larger your API is – the more classes, methods, and arguments you expose – the more ways in which your API can be accessed and coupled to your clients application. As such, the quality of being minimally complete can contribute toward loose coupling.
Q: What issues do you need to be aware of when working with callbacks, observers, and notifications?
A: Some general issues to be aware of when working with callbacks, observers, and notifications:
Q: What is a callback in C/C++?
A: In C/C++, a callback is a pointer to a function within module A that is passed to module B so that B can invoke the function in A at an appropriate time. Keep in mind that the usage of callbacks can be convoluted in object-oriented C++ programs without using libraries such as
Q: What mechanism can you use in object-oriented languages such as C++ as an alternative to callbacks?
A: A more object-oriented solution is to use the observer pattern. In this pattern an object maintains a list of its dependent objects (observers) and notifies them by calling one of their methods.
Q: What are notifications? Give an example of a notification mechanism.
A: Notifications (or events) are sent by a centralized mechanism that serves as an intermediate layer between unconnected parts of the system. There are several kinds of notification schemes such as signals and slots. Signals can be thought of as callbacks with multiple targets (slots). All of the slots for a signal are called when that signal is invoked.