General approach to an interface that will resolve a dependency to a database library
https://softwareengineering.stackexchange.com/questions/333006
-
30-12-2020 - |
سؤال
Background
I often write software for systems that are responsible for testing manufactured products. For every product that gets tested, we have to generate a report for the test-results. The way that we do it now is that we create a .txt
file with the test-results and write that file into some folder on our network drive. Ultimately, I am looking to move toward a database solution such as SQL.
Since I know that we will eventually be moving towards some kind of database solution, I would like to segregate the current functionality (writing the .txt
file to the network drive) by an interface. Eventually I will be able to write a library to control the logging of the test-results to some database. When that happens I would like to simply implement the same interface that the current test-result logging solution will use.
As for the interface, I was thinking to have some simple methods that I know I'll need such as:
LogResults(string operator, string product, string serialNumber, string Results)
Connect(string pathToDriveLocation)
The only thing is, I've never done a database before and I'm not sure if I can adapt the two different ways of performing the logging to the same interface.
Question
Am I thinking about this correctly? Does this seem like a sound approach, or does it need some work?
المحلول
I'd like to take @TulainsCordova's answer one step further.
First, define the interface to be the minimal behavior required to perform logging (I'll use C# as an example):
public interface ILogger
{
void LogResults(string operatorName, string product, string serialNumber, string Results);
}
That's all you need is a way to log stuff. Anything more and your interface gets polluted with implementation details. Any information that is implementation-specific should be handled as a constructor argument to the concrete class that implements this interface. For instance, TextFileLogger:
public class TextFileLogger : ILogger
{
public TextFileLogger(string fileName)
{
this.fileName = fileName;
}
private string fileName;
public void LogResults(string operatorName, string product, string serialNumber, string Results)
{
string message = "...";
File.WriteAllText(fileName, message, Encoding.UTF8);
}
}
Later on you decide to log to a database, so you can add another class:
public class SqlServerDatabaseLogger : ILogger
{
public SqlServerDatabaseLogger(string connectionString)
{
this.connectionString = connectionString;
}
public void LogResults(string operatorName, string product, string serialNumber, string Results)
{
string message = "...";
using (SqlConnection connection = new SqlConnection(connectionString))
{
// INSERT into database
}
}
}
In the rest of your application can be decoupled from the kind of logger by utilizing a factory class (and accompanying enum for the logger type):
public enum LoggerType
{
TextFile = 0,
SqlServer = 1
}
public class LoggerFactory
{
public LoggerFactory(LoggerType loggerType)
{
this.loggerType = loggerType;
}
private ILogger logger;
private LoggerType loggerType;
public ILogger Logger
{
get
{
if (logger == null)
logger = CreateLogger(loggerType);
return logger;
}
}
private static ILogger CreateLogger(LoggerType loggerType)
{
switch (loggerType)
{
case LoggerType.TextFile:
return new TextFileLogger("path/to/filename");
case LoggerType.SqlServer:
return new SqlServerDatabaseLogger("connection string");
default:
throw new ArgumentOutOfRangeException("loggerType");
}
}
}
So create the logger factory once:
LoggerFactory factory = new LoggerFactory(LoggerType.TextFile);
ILogger logger = factory.Logger;
Pass the logger or factory around to any number of other classes. When switching to a new logging type, just change a single line of code:
LoggerFactory factory = new LoggerFactory(LoggerType.SqlServer);
And you're done.
نصائح أخرى
Your approach is good indeed. There should be no conceptual difference (interface-wise) between logging to a text file, to a database table or to a printer (or screen).
In the connect method I suggest you change signature to something more neutral, like:
openDevice(String deviceString);
A devide string cuold be a path to a file...
connect("/var/logs/log001.txt");
or a database connection string...
connect("user/password@10.72.2.10:1021/database");
In the case of file logs you simply validate whether the file exists and you have permission.
You can choose between adding a "throws CouldNotOpenDeviceException" or returning an error code.
void openDevice(String deviceString) throws CouldNotOpenDeviceException;
vs
int openDevice(String deviceString);