OOP方式的鸡蛋问题

时间:2009-04-07 21:22:21

标签: php design-patterns oop

我有鸡蛋问题。我想用OOP方式在PHP中实现一个系统,其中两个类将扮演重要角色:数据库和日志。我的想法是通过Database类建立连接,这将有公共方法,例如。 runQuery(sUpdateQuery),doInsert(sInsert)等.Log类将通过常用方法编写日志,就像logMessage(message),logDatabaseQuery(sQuery)to the DATABASE一样。问题出现了。

1:在Database类的方法中,我希望能够使用Log类的logDatabaseQuery(sQuery)

2:如果我不想在logDatabaseQuery方法中使用Database类的doInsert(sInsert)方法,这仍然不是一个很大的挑战。

我想保持简单 - 只使用数据库连接对象的一个​​实例,如果可能的话,从loggeras中使用。

对于很多人来说,Singleton模型将是第一个选择的想法,但我绝对希望使用不同的解决方案。

因此会有两个类使用彼此的方法:

数据库     doInsert         logDatabaseQuery

日志     logDatabaseQuery        doInsert

我想单独保存Log方法(在Log类中),因为稍后它会有其他方法不仅可以记录到数据库,还可以记录到文件或电子邮件。

任何想法,这应该如何/可以用最好的,OOP友好的方式完成?

我正在考虑一个共同的父抽象类,或者关于使用接口,但最终无法弄清楚正确的方法:(

我想知道的是对正确的类层次结构的建议

13 个答案:

答案 0 :(得分:4)

听起来你有两种不同的数据库访问 - 记录(正常情况)和未记录(对于记录功能)。将数据库类拆分为两个,一个是记录请求的更高级版本,另一个是未记录请求的低级版本。使用对日志记录和较低级别数据库类的引用来实现更高级别的数据库类。

答案 1 :(得分:4)

你把太多东西放在一起了。

数据库实际上不依赖于依赖于数据库的记录器。这不是好设计。

你真正拥有的是两种数据库访问。

低级访问执行“原始”SQL。

记录器可以依赖于这个较低级别的类。它本身没有原始SQL。这取决于较低级别的课程。

高级访问执行应用程序查询,它使用低级访问记录器。

答案 2 :(得分:3)

如果要让数据库对象访问记录器,则将记录器传递给它。如果要让记录器访问数据库对象,则将数据库对象传递给它。

在使用它们提供的函数之前,检查这些对象是否存在于类中。

由于PHP默认情况下通过引用传递这些对象,因此可以使用单个记录器和数据库类为多个对象执行此操作。

我还建议您在记录器中的单个点上进行所有数据库写入。只需将所有内容存储在临时数组中,然后在解构时运行数据库代码。否则,如果您在整个代码中写入数据库,则会产生大量开销。

class Database {
    private $_logger = 0; //contains your logger
    /* The rest of your class goes here */

    //Add the logger to your class so it may access it.
    public function setLogger($logger) {
        $this->_logger = $logger;
    }

    //To check if we have a logger, we don't want to use one if we lack one.
    public function hasLogger() { 
        if($this->_logger) return true; 
        else return false;
    }
}
class Logger {
    private $_database = 0; //variable to hold your database object
    /* The rest of your class goes here */

    public function setDatabase($database) { //Same crap basically
        $this->_database = $database;
    }

    public function hasDatabase() {
        if($this->_database) return true;
        else return false;
    }

    public function doSomething { //This is new though
        if($this->hasDatabase()) { //Shows how you use hasDatabase()
            $this->_database->someDatabaseFunction();
        }
        else {
            //Whatever you'd do without a database connection
        }
    }
}
$database = new Database;
$logger = new Logger;

$database->setLogger($logger);
$logger->setDatabase($database);

答案 3 :(得分:3)

从Logger中解耦数据库。我会将应用程序设计为从中间层进行记录,并决定使用哪种记录器类型 运行时不编译时间。即使用工厂方法确定是否记录到db / xml / whatever。

如果数据层确实需要记录(即报告问题),它会抛出异常,在中间层捕获它,然后决定如何在那里处理它或将其交给诊断类并做出决定。无论哪种方式,我都会尽可能地将DAL保持为“盲目/愚蠢”,而不是让它决定什么是可记录事件和什么不是可记录事件。

一个普通的父类不是一个好主意。记录器不是数据库[r]。并且数据库也不是记录器。它不是鸡和蛋的问题,而是牛和猪的问题。它们是两种不同的动物。数据库没有理由知道记录器。我认为你正在强制抽象。我所看到的只是Logger有一个数据库......如果它是一个db logger就是。

无论如何你从中间层驱动数据层,所以当包含异常和事件时,我无法看到在记录数据库相关事件时你会失去保真度。

 public interface ILoggingProvider
    {
       void Log(String message)
    }

    public static class Logger
    {
       public static ILoggingProvider GetLoggingProvider()
       {
          //factory to return your logger type
       }

       public void Log(String message)
       {
          Logger.GetLoggingProvider().Log(message);
       }
    }

    public class DBLogger : ILoggingProvider {

      void Log(string message) {
       Database db = new Database();
       db.doInsert(someLoggingProc);
     }
    }

    public class Database
    {
        runQuery(sUpdateQuery) 
        doInsert(sInsert)
    }

...


 public class Customer{

 public void SaveCustomer(Customer c)
 {
   try {
    // Build your query
    Database db = new Database();
    db.runQuery(theQuery);
   } catch (SqlException ex) {
    Logger.Log(ex.message);
   }
 }
}

答案 4 :(得分:1)

创建一个实现接口ILogger的Logger类。 Logger类的新实例接收一个Object,该Object实现用于输出日志消息的接口ILoggingProvider。

创建一个实现ILoggingProvider接口的Database类。数据库的新实例接收一个对象,该对象实现用于记录消息的ILogger接口。

public interface ILogger
{
   void Debug(String message)
}

public interface ILoggingProvider
{
   void Log(String message)
}

public class Logger : ILogger
{
   private ILoggingProvider LoggingProvider { get; set; }

   public Logger(ILoggingProvider loggingProvider)
   {
      this.LoggingProvider = loggingProvider;
   }

   public void Debug(String message)
   {
      this.LoggingProvider.Log(message);
   }
}

public class Database : ILoggingProvider
{
   private ILogger Logger { get; set; }

   public Database(ILogger logger)
   {
      this.Logger = logger;
   }

   public void DoStuffWithTheDatabase()
   {
      // Do stuff with the database
      this.Logger.Debug("Did stuff with the database.");
   }

   public void Log(String message)
   {
      // Store message to database - be carefull not to generate new
      // log messages here; you can only use the subset of the Database
      // methods that do not themselve generate log messages
   }
}

答案 5 :(得分:0)

创建一个insert方法重载,允许插入而不记录,并让您的日志记录类使用它。否则,您的设计按定义递归:通过执行数据库插入来记录所有数据库插入。

答案 6 :(得分:0)

doInsert $ callerIsLogger = false添加一个可选参数,然后每次需要从记录器中执行doInsert()时,提供第二个参数true,并且你的doInsert可以检查这种情况而不是在记录器调用时调用记录器。什么等级? KISS方法论岩石:)

答案 7 :(得分:0)

这是Mediator Pattern的好候选人。您将拥有一个对象,记录器和数据库都将调用各种方法,并且此中介对象将保留对记录器和数据库的引用并为您处理通信。

答案 8 :(得分:0)

您可以添加数据库和记录器使用的另一个类,以避免进入无限日志记录循环(并避免循环依赖)。

// Used only by Logger and DatabaseProxy
class Database {
   function doInsert( $sql ) {
      // Do the actual insert.
   }
}

class Logger {
   function log($sql) {
     $msg_sql = ...
     Database.doInsert($msg_sql);
}

// Used by all classes
class DatabaseProxy {
   function doInsert( $sql ) {
      Logger.log($sql);
      Database.doInsert($sql);
   }
}

您可以通过让Database和DatabaseProxy同时实现一个通用接口来装扮它,并使用工厂提供适当的实例。

答案 9 :(得分:0)

恕我直言,数据库类不应与记录器进行交互。我倾向于从调用数据库类的代码的相同部分调用记录器。然后记录器可以使用数据库类来执行其插入。

答案 10 :(得分:0)

听起来你为一个简单的问题添加了很多复杂功能,纯粹是为了让它面向对象......

问问自己,采取这种方法你真正获得了什么?

答案 11 :(得分:0)

您可以为Database::insert()功能制作第二个arg:

function insert($sql, $logThis = true) { ... }

然后很明显当记录器调用它时,将第二个参数设为false。

或者,只需检查函数堆栈以查看是否从Logging类调用了insert函数。

function insert($sql) {
    $callStack = debug_backtrace();
    if (!isset($callStack[1]) || $callStack[1]['class'] !== "Logger") {
        Logger::logSQL($sql);
    }
    // ...
}

答案 12 :(得分:0)

我该怎么做:

public interface ILogger
{
    void LogWarning(string message);
    void LogMessage(string message);
}

public interface ILoggingProvider
{
    void LogEntry(string type, string message);
}

public interface IDbProvider
{
    void DoInsert(string insertQuery, params object[] parameters);
}

public class Logger: ILogger
{
    private ILoggingProvider _provider = ProviderFactory.GetLogProvider();

    public void LogWarning(string message)
    {
        _provider.LogEntry("warn", message);
    }

    public void LogMessage(string message)
    {
        _provider.LogEntry("message", message);
    }

    public void LogEntry(string type, string message)
    {
        _provider.LogEntry(type, message);
    }
}

public class Database : IDbProvider
{
    private Logger _logger = new Logger();
    public void DoInsert(string insertQuery, params object [] parameters)
    {
        _logger.LogEntry("query", insertQuery);
    }
}

public class DbLogProvider : ILoggingProvider
{
    private IDbProvider _dbProvider = ProviderFactory.GetDbProvider();

    public void LogEntry(string type, string message)
    {
        _dbProvider.DoInsert("insert into log(type,message) select @type,@message",type,message);
    }

}

public static class ProviderFactory
{
    public static IDbProvider GetDbProvider()
    {
        return new Database();
    }


    internal static ILoggingProvider GetLogProvider()
    {
        return new DbLogProvider();
    }
}

虽然我可能也会让它从配置文件中查找一个类型,而不是在ProviderFactory类中对它们进行硬编码。这里的假设是你的代码不关心它是如何被记录的,这是管理员,并且所有日志记录都将在执行期间以一种方式完成(无论如何我都会这样做)。显然,您可以扩展它以在创建记录器和创建适当的提供者时选择日志目标。