The matter of how layers should be realized is determined by the client-server nature of the relationship between an application and its platform/virtual machine. This includes some characteristics of layers discussed previously:
Asymmetry:
Interface-based interactions:
Domain-specific services grouping:
To illustrate how applications use the interface of a layer to achieve specific goals, let's consider a few examples across different domains. These examples will demonstrate how applications interact with a layer's interface to utilize its services, ensuring that all interactions are encapsulated within the layer's interface.
An application needs to perform CRUD (Create, Read, Update, Delete) operations on a database. The database access layer provides an interface for these operations.
class IDatabase {
public:
virtual ~IDatabase() = default;
virtual void connect(const std::string& connectionString) = 0;
virtual void disconnect() = 0;
virtual void executeQuery(const std::string& query) = 0;
virtual std::vector<std::string> fetchResults() = 0;
};
class MySQLDatabase : public IDatabase {
public:
void connect(const std::string& connectionString) override {
// Implementation for connecting to MySQL database
}
void disconnect() override {
// Implementation for disconnecting from MySQL database
}
void executeQuery(const std::string& query) override {
// Implementation for executing a query on MySQL database
}
std::vector<std::string> fetchResults() override {
// Implementation for fetching results from MySQL database
return {};
}
};
class Application {
public:
Application(IDatabase* db) : database(db) {}
void run() {
database->connect("connection_string");
database->executeQuery("SELECT * FROM users");
auto results = database->fetchResults();
for (const auto& result : results) {
std::cout << result << std::endl;
}
database->disconnect();
}
private:
IDatabase* database;
};
int main() {
MySQLDatabase db;
Application app(&db);
app.run();
return 0;
}
An application needs to log messages for debugging and auditing purposes. The logging layer provides an interface for logging messages.
class ILogger {
public:
virtual ~ILogger() = default;
virtual void logInfo(const std::string& message) = 0;
virtual void logWarning(const std::string& message) = 0;
virtual void logError(const std::string& message) = 0;
};
class ConsoleLogger : public ILogger {
public:
void logInfo(const std::string& message) override {
std::cout << "[INFO] " << message << std::endl;
}
void logWarning(const std::string& message) override {
std::cout << "[WARNING] " << message << std::endl;
}
void logError(const std::string& message) override {
std::cout << "[ERROR] " << message << std::endl;
}
};
class Application {
public:
Application(ILogger* logger) : logger(logger) {}
void run() {
logger->logInfo("Application started");
// Perform some operations
logger->logWarning("This is a warning message");
// Perform some more operations
logger->logError("This is an error message");
logger->logInfo("Application finished");
}
private:
ILogger* logger;
};
int main() {
ConsoleLogger logger;
Application app(&logger);
app.run();
return 0;
}
An application needs to send and receive data over the network. The network communication layer provides an interface for network operations.
class INetwork {
public:
virtual ~INetwork() = default;
virtual void connect(const std::string& address, int port) = 0;
virtual void disconnect() = 0;
virtual void sendData(const std::string& data) = 0;
virtual std::string receiveData() = 0;
};
class TCPNetwork : public INetwork {
public:
void connect(const std::string& address, int port) override {
// Implementation for connecting to a TCP server
}
void disconnect() override {
// Implementation for disconnecting from a TCP server
}
void sendData(const std::string& data) override {
// Implementation for sending data over TCP
}
std::string receiveData() override {
// Implementation for receiving data over TCP
return {};
}
};
class Application {
public:
Application(INetwork* network) : network(network) {}
void run() {
network->connect("127.0.0.1", 8080);
network->sendData("Hello, Server!");
auto response = network->receiveData();
std::cout << "Received: " << response << std::endl;
network->disconnect();
}
private:
INetwork* network;
};
int main() {
TCPNetwork network;
Application app(&network);
app.run();
return 0;
}
In these examples, the application interacts with different layers (database access, logging, network communication) through well-defined interfaces. These interfaces provide the necessary services for the application to achieve its goals. The implementation details of each layer are hidden behind the interface, ensuring that the application remains stable even if the underlying implementation changes. This approach promotes modularity, maintainability, and flexibility in software design.