同步异步日志系统:设计模式
设计模式是前辈们对代码开发经验的总结,是解决特定问题的⼀系列套路。它不是语法规定,⽽是⼀ 套⽤来提⾼代码可复⽤性、可维护性、可读性、稳健性以及安全性的解决⽅案。
为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打 仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后 来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
一、六大原则
1.1 单一职责原则
单⼀职责原则(Single Responsibility Principle):类的职责应该单⼀,⼀个⽅法只做⼀件事。职责划分清晰了,每次改动到最⼩单位的⽅法或类。
使⽤建议:两个完全不一样的功能不应该放⼀个类中,⼀个类中应该是⼀组相关性很⾼的函 数、数据的封装
用例:网络聊天:⽹络通信&聊天,应该分割成为网络通信类&聊天类
这样如果我们不用网络通信了,就不需要修改聊天相关的代码
1.2 开闭原则
开闭原则(Open Closed Principle):对扩展开放,对修改封闭
使用建议:对软件实体的改动,最好用扩展而非修改的方式。
用例:超时卖货:商品价格---不是修改商品的原来价格,而是新增促销价格。
临期过期商品需要低价处理的时候,不是修改原价格,而是新增一个促销价格,这样后期的时候可以直接去掉促销价格而不需要去修改原价格
1.3 里氏替换原则
⾥⽒替换原则(Liskov Substitution Principle):通俗点讲,就是只要⽗类能出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或异常。
动机:它鼓励我们在设计类时,应该关注类的抽象,而不是具体的实现,这样当我们需要对类进行扩展时,就可以通过创建新的子类来完成,而不是直接修改基类。这种设计方式有助于减少代码的耦合度,提高代码的可维护性和可扩展性。
动机的背后是面向对象设计中的一个基本矛盾:一方面,我们希望类的功能是封闭的,即一个类应该只关注自己的业务逻辑,而不关心其他类的细节;另一方面,我们希望类的功能是可扩展的,即在不修改原有代码的情况下,能够方便地对类进行扩展。里氏代换原则正是为了解决这个矛盾而提出的。在继承类时,务必重写⽗类中所有的⽅法,尤其需要注意⽗类的protected⽅法,⼦类尽量不要 暴露⾃⼰的public⽅法供外界调⽤。
使用建议:子类必须完全实现父类的⽅法,孩⼦类可以有自己的个性。覆盖或实现⽗类的⽅法 时,输⼊参数可以被放大,输出可以缩小
用例:跑步运动员类-会跑步,⼦类⻓跑运动员-会跑步且擅长长跑,子类短跑运动员-会跑步且擅长短跑
1.4 依赖倒置原则
依赖倒置原则(Dependence Inversion Principle):⾼层模块不应该依赖低层模块,两者都应该依赖其抽象.不可分割的原⼦逻辑就是低层模式,原子逻辑组装成的就是⾼层模块。
模块间依赖通过抽象(接口)发生,具体类之间不直接依赖
使用建议:每个类都尽量有抽象类,任何类都不应该从具体类派⽣。尽量不要重写基类的方法。结合里⽒替换原则使用。
用例:奔驰车司机类--只能开奔驰;司机类--给什么车,就开什么车;开车的⼈:司机--依赖于抽象
1.5 迪米特法则
迪⽶特法则(Law of Demeter),⼜叫“最少知道法则”:尽量减少对象之间的交互,从⽽减⼩类之间的耦合。⼀个对象应该对其他对象有最少的了解。 对类的低耦合提出了明确的要求——只和直接的朋友交流,朋友之间也是有距离的。⾃⼰的就是⾃⼰的(如果⼀个⽅法放在本类 中,既不增加类间关系,也对本类不产⽣负⾯影响,那就放置在本类中)。
⽤例:⽼师让班⻓点名--⽼师给班⻓⼀个名单,班⻓完成点名勾选,返回结果,⽽不是班⻓点名,⽼师勾选
1.6 接⼝隔离原则
接⼝隔离原则(Interface Segregation Principle):客⼾端不应该依赖它不需要的接口,类间的依赖关系应该建立在最⼩的接口上
使用建议:接⼝设计尽量精简单⼀,但是不要对外暴露没有实际意义的接⼝。
用例:修改密码,不应该提供修改⽤⼾信息接⼝,⽽就是单⼀的最⼩修改密码接⼝,更不要暴露数据库操作
1.7 总结
从整体上来理解六⼤设计原则,可以简要的概括为⼀句话,⽤抽象构建框架,⽤实现扩展细节,具体 到每⼀条设计原则,则对应⼀条注意事项:
单⼀职责原则告诉我们实现类要职责单⼀;
⾥⽒替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要⾯向接⼝编程;
接⼝隔离原则告诉我们在设计接⼝的时候要精简单⼀;
迪⽶特法则告诉我们要降低耦合;
开闭原则是总纲,告诉我们要对扩展开放,对修改关闭。
二、单例模式
⼀个类只能创建⼀个对象,即单例模式,该设计模式可以保证系统中该类只有⼀个实例,并提供⼀个访问它的全局访问点,该实例被所有程序模块共享。(像数据库连接池、日志系统、线程池会特别需要)
1、把这些数据都放到一个类里面,把这个类设计成单例类
⽐如在某个服务器程序中,该服务器的配置信息存放在⼀个⽂件中,这些配置数据由⼀个单例对象统⼀读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种⽅式简化了在复杂环境下的配置管理。
2、构造、拷贝构造和赋值重载都封死,提供一个static公有获取单例对象的函数
3、单例模式有两种实现模式:饿汉模式和懒汉模式
2.1 饿汉模式
饿汉模式:程序启动时就会创建⼀个唯⼀的实例对象。
因为单例对象已经确定,所以⽐较适⽤于多线程环境中,多线程获取单例对象不需要加锁,可以有效的避免资源竞争,提⾼性能。
// 饿汉模式
#include<iostream>
class Singleton {
private:
static Singleton _eton;
private:
Singleton()
{
std::cout<<"Singleton()"<<std::endl;
}
~Singleton()
{
std::cout<<"~Singleton()"<<std::endl;
}
public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton& getInstance()
{
return _eton;
}
};
Singleton Singleton::_eton;
int main()
{
std::cout<<"hello"<<std::endl;
Singleton &s1 = Singleton::getInstance();
return 0;
}
2.2 懒汉模式
第⼀次使用要使用单例对象的时候创建实例对象。如果单例对象构造特别耗时或者耗费资源(加载插件、加载网络资源等),可以选择懒汉模式,在第⼀次使用的时候才创建对象。
2.2.1 C++11之前
常规实现思路:定义一个静态对象的指针,在访问接口的时候发现他为空才去new一个对象,但是这样会存在线程竞争需要加锁,但是加锁又会有锁冲突,就还得再加一层检测
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
class Singleton
{
public:
static Singleton* GetInstance() {
// 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全
if (nullptr == m_pInstance) { //这是为了保证效率
m_mtx.lock();
if (nullptr == m_pInstance) m_pInstance = new Singleton(); //保证线程安全
m_mtx.unlock();
}
return m_pInstance;
}
// 一般全局都要使用单例对象,所以单例对象一般不需要显示释放
// 有些特殊场景,想显示释放一下
static void DelInstance()
{
m_mtx.lock();
if (_ins)
{
delete _ins;
_ins = nullptr;
}
m_mtx.unlock();
}
// 实现一个内嵌垃圾回收类
class CGarbo { //因为该静态对象是一个指针所以并不会调用析构 所以我们用一个内部类帮我们调用
public:
~CGarbo(){
DelInstance();
}
};
// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo Garbo;
~Singleton()
{
// 一般不需要 但是有些时候会有持久化需求
// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好
}
private:
// 构造函数私有
Singleton(){};
// 防拷贝
Singleton(Singleton const&);
Singleton& operator=(Singleton const&);
static Singleton* m_pInstance; // 单例对象指针
static mutex m_mtx; //互斥锁
};
Singleton* Singleton::m_pInstance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;
int main()
{
thread t1([]{cout << &Singleton::GetInstance() << endl; });
thread t2([]{cout << &Singleton::GetInstance() << endl; });
t1.join();
t2.join();
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
return 0;
}
注意1:指针并不会自动调用析构!!所以需要我们手动进行delete!(如果是饿汉创建的是对象的话会自己析构,但是懒汉必须得是指针,所以必须手动delete)但是我们很可能会忘记,所以我们可以搞一个内嵌的垃圾回收类,然后由他的析构去帮助我们delete!他的变量也是静态的,声明周期和单例对象一样!!
注意2:单例对象一般不需要显式去delete,而是会随着程序结束而结束,但是有些场景下我们可能会需要显式释放一下,那我们就可以去封装一个Delinstance,来帮我们完成delete的操作
注意3:析构函数一般也不需要写,但是在有些情况比方说需要有一些持久化保存,比方说程序结束的时候需要将数据写到文件!!那么这个操作就要写到析构函数里面
注意4:构造的时候双判断,第一个判断是为了提高效率的(如果没有也行,但是会导致每一次调用无论创不创建都要经过加锁和解锁,如果有的话那么只有第一次创建的会加锁解锁),第二个判断是为了线程安全加锁的!!
2.2.2 C++11之后
特殊思路:这⾥介绍的是《Effective C++》⼀书作者ScottMeyers提出的⼀种更加优雅简便的单例模式Meyers' Singleton in C++。C++11 Static local variables特性以确保C++11起,局部静态变量将能够在满⾜thread-safe的前提下唯⼀地被构造和析构
//懒汉模式
#include<iostream>
class Singleton {
private:
Singleton()
{
std::cout<<"Singleton()"<<std::endl;
}
~Singleton()
{
std::cout<<"~Singleton()"<<std::endl;
}
public:
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton& getInstance()
{
static Singleton _eton;
return _eton;
}
};
int main()
{
std::cout<<"hello"<<std::endl;
Singleton &s1 = Singleton::getInstance();
return 0;
}
2.3 饿汉vs懒汉(优缺点)
饿汉的缺点:
1、如果单例对象初始化很慢(如初始化动作多,还会伴随一些IO行为,比如读取配置文件等),那么在main函数之前就要申请。由于暂时不需要使用确占用了资源,其次程序启动速度会受影响
2、如果两个单例都是饿汉,并且有依赖关系,要求单例1再创建,单例2再创建,饿汉无法控制顺序,懒汉才可以。
饿汉的优点:更加简单!
懒汉完美解决了饿汉的所有问题,只不过设计上相对复杂一点!
三、工厂模式
工厂模式是⼀种创建型设计模式,它提供了⼀种创建对象的最佳⽅式。在⼯⼚模式中,我们创建对象是不会对上层暴露创建逻辑,⽽是通过使⽤⼀个共同结构来指向新创建的对象,以此实现创建-使用的 分离。
3.1 简单工厂模式
简单⼯⼚模式:简单⼯⼚模式实现由⼀个工厂对象通过类型决定创建出来指定产品类的实例。假设 有个⼯⼚能⽣产出⽔果,当客⼾需要产品的时候明确告知⼯⼚⽣产哪类⽔果,工厂需要接收⽤⼾提 供的类别信息,当新增产品的时候,工厂内部去添加新产品的生产方式。
//简单⼯⼚模式:通过参数控制可以⽣产任何产品
// 优点:简单粗暴,直观易懂。使⽤⼀个⼯⼚⽣产同⼀等级结构下的任意产品
// 缺点:
// 1. 所有东西⽣产在⼀起,产品太多会导致代码量庞⼤
// 2. 开闭原则遵循(开放拓展,关闭修改)的不是太好,要新增产品就必须修改⼯⼚⽅法。
#include <iostream>
#include <string>
#include <memory>
class Fruit {
public:
Fruit(){}
virtual void show() = 0;
};
class Apple : public Fruit {
public:
Apple() {}
virtual void show() {
std::cout << "我是一个苹果" << std::endl;
}
};
class Banana : public Fruit {
public:
Banana() {}
virtual void show() {
std::cout << "我是一个香蕉" << std::endl;
}
};
class FruitFactory {
public:
static std::shared_ptr<Fruit> create(const std::string &name) {
if (name == "苹果") {
return std::make_shared<Apple>();
}else if(name == "香蕉") {
return std::make_shared<Banana>();
}
return std::shared_ptr<Fruit>();
}
};
int main()
{
std::shared_ptr<Fruit> fruit = FruitFactory::create("苹果");
fruit->show();
fruit = FruitFactory::create("香蕉");
fruit->show();
return 0;
}
这个模式的结构和管理产品对象的⽅式⼗分简单,但是它的扩展性⾮常差,当我们需要新增产品的时候,就需要去修改⼯⼚类新增⼀个类型的产品创建逻辑,违背了开闭原则。
3.2 工厂方法模式
⼯⼚⽅法模式:在简单⼯⼚模式下新增多个⼯⼚,多个产品,每个产品对应⼀个⼯⼚。假设现在有A、B两种产品,则开两个⼯⼚,⼯⼚A负责⽣产产品A,⼯⼚B负责⽣产产品B,⽤⼾只知道产品 的⼯⼚名,⽽不知道具体的产品信息,⼯⼚不需要再接收客⼾的产品类别,⽽只负责⽣产产品。
//⼯⼚⽅法:定义⼀个创建对象的接⼝,但是由⼦类来决定创建哪种对象,使⽤多个⼯⼚分别⽣产指定 的固定产品
// 优点:
// 1. 减轻了⼯⼚类的负担,将某类产品的⽣产交给指定的⼯⼚来进⾏
// 2. 开闭原则遵循较好,添加新产品只需要新增产品的⼯⼚即可,不需要修改原先的⼯⼚类
// 缺点:对于某种可以形成⼀组产品族的情况处理较为复杂,需要创建⼤量的⼯⼚类.
#include <iostream>
#include <string>
#include <memory>
class Fruit {
public:
Fruit(){}
virtual void show() = 0;
};
class Apple : public Fruit {
public:
Apple() {}
virtual void show() {
std::cout << "我是一个苹果" << std::endl;
}
private:
std::string _color;
};
class Banana : public Fruit {
public:
Banana() {}
virtual void show() {
std::cout << "我是一个香蕉" << std::endl;
}
};
class FruitFactory {
public:
virtual std::shared_ptr<Fruit> create() = 0;
};
class AppleFactory : public FruitFactory {
public:
virtual std::shared_ptr<Fruit> create() {
return std::make_shared<Apple>();
}
};
class BananaFactory : public FruitFactory {
public:
virtual std::shared_ptr<Fruit> create() {
return std::make_shared<Banana>();
}
};
int main()
{
std::shared_ptr<FruitFactory> factory(new AppleFactory());
std::shared_ptr<Fruit> fruit = factory->create();
fruit->show();
factory.reset(new BananaFactory());
fruit = factory->create();
fruit->show();
return 0;
}
⼯⼚⽅法模式每次增加⼀个产品时,都需要增加⼀个具体产品类和⼯⼚类,这会使得系统中类的个数成倍增加,在⼀定程度上增加了系统的耦合度。
3.3 抽象工厂模式
抽象⼯⼚模式:⼯⼚⽅法模式通过引⼊⼯⼚等级结构,解决了简单⼯⼚模式中⼯⼚类职责太重的问 题,但由于⼯⼚⽅法模式中的每个⼯⼚只⽣产⼀类产品,可能会导致系统中存在⼤量的⼯⼚类,势 必会增加系统的开销。此时,我们可以考虑将⼀些相关的产品组成⼀个产品族(位于不同产品等级 结构中功能相关联的产品组成的家族),由同⼀个⼯⼚来统⼀⽣产,这就是抽象⼯⼚模式的基本思 想。
//抽象⼯⼚:围绕⼀个超级⼯⼚创建其他⼯⼚。每个⽣成的⼯⼚按照⼯⼚模式提供对象。
// 思想:将⼯⼚抽象成两层,抽象⼯⼚ & 具体⼯⼚⼦类, 在⼯⼚⼦类种⽣产不同类型的⼦产品
#include <iostream>
#include <string>
#include <memory>
class Fruit {
public:
Fruit(){}
virtual void show() = 0;
};
class Apple : public Fruit {
public:
Apple() {}
virtual void show() {
std::cout << "我是一个苹果 " << std::endl;
}
private:
std::string _color;
};
class Banana : public Fruit {
public:
Banana() {}
virtual void show() {
std::cout << "我是一个香蕉" << std::endl;
}
};
class Animal {
public:
virtual void voice() = 0;
};
class Lamp: public Animal {
public:
void voice() { std::cout << "咩咩咩\n"; }
};
class Dog: public Animal {
public:
void voice() { std::cout << "汪汪汪\n"; }
};
class Factory {
public:
virtual std::shared_ptr<Fruit> getFruit(const std::string &name) = 0;
virtual std::shared_ptr<Animal> getAnimal(const std::string &name) = 0;
};
class FruitFactory : public Factory {
public:
virtual std::shared_ptr<Animal> getAnimal(const std::string &name) {
return std::shared_ptr<Animal>();
}
virtual std::shared_ptr<Fruit> getFruit(const std::string &name) {
if (name == "苹果") {
return std::make_shared<Apple>();
}else if(name == "⾹蕉") {
return std::make_shared<Banana>();
}
return std::shared_ptr<Fruit>();
}
};
class AnimalFactory : public Factory {
public:
virtual std::shared_ptr<Fruit> getFruit(const std::string &name) {
return std::shared_ptr<Fruit>();
}
virtual std::shared_ptr<Animal> getAnimal(const std::string &name) {
if (name == "⼩⽺") {
return std::make_shared<Lamp>();
}else if(name == "⼩狗") {
return std::make_shared<Dog>();
}
return std::shared_ptr<Animal>();
}
};
class FactoryProducer {
public:
static std::shared_ptr<Factory> getFactory(const std::string &name) {
if (name == "动物") {
return std::make_shared<AnimalFactory>();
}else {
return std::make_shared<FruitFactory>();
}
}
};
int main()
{
std::shared_ptr<Factory> fruit_factory = FactoryProducer::getFactory("⽔果");
std::shared_ptr<Fruit> fruit = fruit_factory->getFruit("苹果");
fruit->show();
fruit = fruit_factory->getFruit("⾹蕉");
fruit->show();
std::shared_ptr<Factory> animal_factory = FactoryProducer::getFactory("动物");
std::shared_ptr<Animal> animal = animal_factory->getAnimal("⼩⽺");
animal->voice();
animal = animal_factory->getAnimal("⼩狗");
animal->voice();
return 0;
}
抽象⼯⼚模式适⽤于⽣产多个⼯⼚系列产品衍⽣的设计模式,增加新的产品等级结构复杂,需要对原 有系统进⾏较⼤的修改,甚⾄需要修改抽象层代码,违背了“开闭原则”。
四、建造者模式
建造者模式是⼀种创建型设计模式,使⽤多个简单的对象⼀步⼀步构建成⼀个复杂的对象,能够将⼀ 个复杂的对象的构建与它的表⽰分离,提供⼀种创建对象的最佳⽅式。主要⽤于解决对象的构建过于复杂的问题。
工厂模式核心在于创建一个对象,而建造者模式核心在于构建一个对象!!当构建对像过于复杂且对内部零件有顺序要求时,就可以用建造者模式
建造者模式主要基于四个核⼼类实现:
• 抽象产品类:
• 具体产品类:⼀个具体的产品对象类
• 抽象Builder类:创建⼀个产品对象所需的各个部件的抽象接⼝
• 具体产品的Builder类:实现抽象接⼝,构建各个部件
• 指挥者Director类:统⼀组建过程,提供给调⽤者使⽤,通过指挥者来构造产品
#include <iostream>
#include <memory>
/*抽象电脑类*/
class Computer {
public:
using ptr = std::shared_ptr<Computer>;
Computer() {}
void setBoard(const std::string &board) {_board = board;}
void setDisplay(const std::string &display) {_display = display;}
virtual void setOs() = 0;
std::string toString() {
std::string computer = "Computer:{\n";
computer += "\tboard=" + _board + ",\n";
computer += "\tdisplay=" + _display + ",\n";
computer += "\tOs=" + _os + ",\n";
computer += "}\n";
return computer;
}
protected:
std::string _board;
std::string _display;
std::string _os;
};
/*具体产品类*/
class MacBook : public Computer {
public:
using ptr = std::shared_ptr<MacBook>;
MacBook() {}
virtual void setOs() {
_os = "Max Os X12";
}
};
/*抽象建造者类:包含创建⼀个产品对象的各个部件的抽象接⼝*/
class Builder {
public:
using ptr = std::shared_ptr<Builder>;
virtual void buildBoard(const std::string &board) = 0;
virtual void buildDisplay(const std::string &display) = 0;
virtual void buildOs() = 0;
virtual Computer::ptr build() = 0;
};
/*具体产品的具体建造者类:实现抽象接⼝,构建和组装各个部件*/
class MacBookBuilder : public Builder {
public:
using ptr = std::shared_ptr<MacBookBuilder>;
MacBookBuilder(): _computer(new MacBook()) {}
virtual void buildBoard(const std::string &board) {
_computer->setBoard(board);
}
virtual void buildDisplay(const std::string &display) {
_computer->setDisplay(display);
}
virtual void buildOs() {
_computer->setOs();
}
virtual Computer::ptr build() {
return _computer;
}
private:
Computer::ptr _computer;
};
/*指挥者类,提供给调⽤者使⽤,通过指挥者来构造复杂产品*/
class Director {
public:
Director(Builder* builder):_builder(builder){}
void construct(const std::string &board, const std::string &display) {
_builder->buildBoard(board);
_builder->buildDisplay(display);
_builder->buildOs();
}
private:
Builder::ptr _builder;
};
int main()
{
Builder *buidler = new MacBookBuilder();
std::unique_ptr<Director> pd(new Director(buidler));
pd->construct("英特尔主板", "VOC显示器");
Computer::ptr computer = buidler->build();
std::cout << computer->toString();
return 0;
}
五、代理模式
代理模式指代理控制对其他对象的访问,也就是代理对象控制对原对象的引⽤。在某些情况下,⼀个 对象不适合或者不能直接被引⽤访问,⽽代理对象可以在客⼾端和⽬标对象之间起到中介的作⽤。 代理模式的结构包括⼀个是真正的你要访问的对象(⽬标类)、⼀个是代理对象。⽬标对象与代理对象实 现同⼀个接⼝,先访问代理类再通过代理类访问⽬标对象。代理模式分为静态代理、动态代理:
• 静态代理指的是,在编译时就已经确定好了代理类和被代理类的关系。也就是说,在编译时就已经 确定了代理类要代理的是哪个被代理类。
• 动态代理指的是,在运⾏时才动态⽣成代理类,并将其与被代理类绑定。这意味着,在运⾏时才能 确定代理类要代理的是哪个被代理类。
以租房为例,房东将房⼦租出去,但是要租房⼦出去,需要发布招租启⽰,带⼈看房,负责维修,这 些⼯作中有些操作并⾮房东能完成,因此房东为了图省事,将房⼦委托给中介进⾏租赁。代理模式实现:
/*房东要把一个房子通过中介租出去理解代理模式*/
#include <iostream>
#include <string>
class RentHouse {
public:
virtual void rentHouse() = 0;
};
/*房东类:将房子租出去*/
class Landlord : public RentHouse {
public:
void rentHouse() {
std::cout << "将房子租出去\n";
}
};
/*中介代理类:对租房子进行功能加强,实现租房以外的其他功能*/
class Intermediary : public RentHouse {
public:
void rentHouse() {
std::cout << "发布招租启示\n";
std::cout << "带人看房\n";
_landlord.rentHouse();
std::cout << "负责租后维修\n";
}
private:
Landlord _landlord;
};
int main()
{
Intermediary intermediary;
intermediary.rentHouse();
return 0;
}