UrlDownloader it shouldn't:
* Make a connection -> We should have separate object for that like TcpConnection
* Display it -> We should have separate class to do that like Canvas
* Modifying the response, like resizing, it should be done by other class like Bitmap
* Cache it -> We should have other class to store cached images like FileCache
* Advantages:
* Testing – A class with one responsibility will have far fewer test cases.
* Lower coupling – Less functionality in a single class will have fewer dependencies.
* Organization – Smaller, well-organized classes are easier to search than monolithic ones.
___
## S - Single resposibility (2)
Let's say we want to create a program that downloads images and allows us to display them. In such cases we need to implement a few classes that are responsible for:
* Download object: Create request, make connection, download raw bits
* ImageDownloader, TcpConnection class
* Image representation: Store info about image, bitmap, allow to modify it.
* Image, Bitmap class
* User interface: Print object
* Canvas, Window class
* Cache: Cache object for future usage and quick access
* FileStorage, RamStorage class
___
## S - Single resposibility (3)
* By following of rule one responsibility let's create Image class. This class will be responsible for:
* Store bitmap
* Store url of image to allow identification of it
* This class don't need to know how to display an image or resize it
```C++
class Image {
public:
Image(const std::string& url, const Bitmap& bitmap)
: url_(url), bitmap_(bitmap) {}
Image(const std::string& url, Bitmap&& bitmap)
: url_(url), bitmap_(std::move(bitmap)) {}
const Bitmap& bitmap() const { return bitmap_; }
const std::string& url() const { return url_; }
private:
std::string url_;
Bitmap bitmap_;
};
```
___
Let's create a second class form this module, a Bitmap class. This class will be responsible for:
* Store raw bit
* Store type of image (png, jpg)
* Store additional info about image like: size, name, url etc...
```C++
class Bitmap {
public:
struct Pixel { uint8_t r; uint8_t g; uint8_t b; }
enum class Type { PNG, JPG };
Image(size_t width, size_t height, Type type, std::vectorCanvas class. This class will be responsible for:
* Knowing how to display bitmap
* Store additional info about canvas like: size
* This class don't need to know how to transform image or resize it.
```C++
class Canvas {
public:
Canvas(size_t width, size_t height) : _width(width), _height(height) {}
void print(const Image& img) const {
const auto& bitmap = img.bitmap();
const size_t imgWidth = bitmap.width();
const size_t imgHeight = bitmap.height();
if (imgWidth > _width || imgHeight > _height) {
bitmap.resize(_width, _height);
}
....
}
private:
size_t _width;
size_t _height;
};
```
* Does Canvas follow the rule of single responsibility?
___
Once again no. Canvas is tightly coupled with types of images, so whenever we add a new type of image we need to rewrite methods. As you can see, one change (adding a new type of image) demands a user to reimplement the `Canvas` class and `Bitmap` class and probably even more classes. This is not a single responsibility. The class should have one reason to change and now class has a lot of reasons to change. Once again I will show you later how we can resolve it.
___
## Single resposibility in STL
SOLID is also valid for static polymorphism. As an example, we can check the STL algorithm like `std::stransform`.
```C++
templateImage class which stores information about `URL` and `bitmap`, now we have a task that we should also have the possibility to store info about:
* date when the image was downloaded
* date when the image was created originally
* a place where the image was taken
We need to ask ourselves whether these pieces of information are required for the existing code or not.
* If required, we probably need to modify the class `Image` extend an interface, and add new fields. But still, this is done by extension of already implemented code. Other classes still compile, and we decide which class should use a new functionality.
* If not required, we probably need to add a derived class that will have this information and use it in a new part of the code that we implement. So we don't modify anything from existing code, we just add a new functionality.
```C++
class DescribedImage : public Image {
public:
DescribedImage(const std::string& url, const Bitmap& bitmap, DateTime downloadTime, DateTime imageTime, Coordinate place):
Image(url, bitmap), _downloadTime(downloadTime), _imageTime(imageTime), _place(place) {}
const DateTime& downloadTime() const { return _downloadTime; }
const DateTime& imageTime() const { return _imageTime; }
const Coordinate& place() const { return _place; }
private:
DateTime _downloadTime;
DateTime _imageTime;
Coordinate _place;
};
```
___
## O - Open-Closed (3)
When we depend on interfaces, we can also add easily new functionality by extending them. Unfortunately, if other classes inherit from these interfaces we need to write the implementation for each class, which requires modification of existing code and we break the rule `open-close` in such a way we need to think about what will change often: types or methods?
* If types we can use for instance a strategy design pattern, because when we create a new type, we need to create a new strategy only for this type and we don't modify anything in existing code.
* If methods we can use for instance a visitor design pattern, because when we add a new method, we need to implement a visit method only for this new one, but the existing code doesn't change.
* I will describe more these patterns in the section design patterns
___
## Visitor
```C++
class BitmapVisitor {
public:
virtual ~BitmapVisitor() = default;
virtual void visit(JPGImage& image, /* other args */) = 0;
virtual void visit(PNGImage& image, /* other args */) = 0;
virtual void visit(GIFImage& image, /* other args */) = 0;
virtual void visit(SVGImage& image, /* other args */) = 0;
};
```
```C++
class ResizeVisitor : public BitmapVisitor {
public:
void visit(JPGImage& image, /* other args */) override;
void visit(PNGImage& image, /* other args */) override;
void visit(GIFImage& image, /* other args */) override;
void visit(SVGImage& image, /* other args */) override;
};
class RotateVisitor : public BitmapVisitor {
public:
void visit(JPGImage& image, /* other args */) override;
void visit(PNGImage& image, /* other args */) override;
void visit(GIFImage& image, /* other args */) override;
void visit(SVGImage& image, /* other args */) override;
};
```
___
## Strategy
```C++
class DownloadStrategy {
public:
virtual ~DownloadStrategy() = default;
using DownloadToVectorCallback = std::function
___
## MVC
The good example of dependency inversion is model-view-controller. Model should be a high level because it will rarely be modified, but view and controller should be on low level because we will continuously add new functionality there. Additionally, we should create two interfaces in the module, one for communication with Controller and the second for communication with view.
___
## STL for the fifth time
And let's back to the `std::transform` method. We represent a set of requirements for each iterator. We demand comparison, dereference etc… if we provide such implementation it will work. Furthermore, we don't need to know what exactly is passed here until it satisfies interface. So `transform` don't depend on low level modules we need to satisfy interface to compile the code.
```C++
template