Яйцо-курица в ООП - PullRequest
       17

Яйцо-курица в ООП

1 голос
/ 08 апреля 2009

У меня проблема с куриным яйцом. Я бы тоже хотел реализовать систему в PHP ООП, в которой два класса будут играть важную роль: база данных и журнал. Моя идея состояла в том, чтобы создать соединение с помощью класса Database, который будет иметь открытые методы, например. runQuery (sUpdateQuery), doInsert (sInsert) и т. д. Класс Log будет записывать журналы с помощью общих методов так же, как logMessage (сообщение), logDatabaseQuery (sQuery) в базу данных. Проблема наступает сейчас.

1: Внутри методов класса Database я хотел бы иметь возможность использовать logDatabaseQuery (sQuery) класса Log

2: Это все равно не будет большой проблемой, если я не захочу использовать метод doInsert (sInsert) класса Database внутри метода logDatabaseQuery.

Я хотел бы сохранить простоту - и просто использовать один экземпляр объекта подключения к базе данных, а также из журналов, если это возможно.

Для многих людей первой идеей была бы модель Singleton, но я определенно хотел бы использовать другое решение.

Таким образом, будет два класса, которые будут использовать методы друг друга:

База данных doInsert logDatabaseQuery

Log logDatabaseQuery doInsert

Я хотел бы хранить методы Log отдельно (в классе Log), поскольку позже у него будут другие методы для входа не только в базу данных, но и в файлы или электронную почту.

Есть идеи, как это можно / можно сделать самым приятным, ООП-дружественным способом?

Я думал об общем родительском абстрактном классе или об использовании интерфейсов, но, наконец, не смог найти правильный путь :(

Что я хотел бы знать, так это предложение по правильной иерархии классов

Ответы [ 13 ]

4 голосов
/ 08 апреля 2009

Похоже, у вас есть два разных доступа к базе данных - зарегистрированный (обычный случай) и незарегистрированный (для функций ведения журнала). Разделите класс базы данных на два: версия более высокого уровня для зарегистрированных запросов и версия более низкого уровня для незарегистрированных запросов. Реализуйте класс базы данных более высокого уровня, используя ссылки на классы журналирования и базы данных более низкого уровня.

4 голосов
/ 08 апреля 2009

Вы объединили слишком много вещей.

База данных не может зависеть от регистратора, который зависит от базы данных. Это не хороший дизайн.

На самом деле у вас есть два вида доступа к базе данных.

Низкоуровневый доступ делает «сырой» SQL.

Регистратор может зависеть от этого класса более низкого уровня. Сам по себе он не имеет сырого SQL. Это зависит от класса более низкого уровня.

Высокоуровневый доступ выполняет запросы приложений, он использует низкоуровневый доступ и регистратор.

3 голосов
/ 08 апреля 2009

Отделение базы данных от регистратора. Я бы разработал приложение для входа со среднего уровня и принял решение о том, какой тип регистратора использовать на время выполнения не время компиляции. То есть используйте фабричный метод, чтобы определить, нужно ли регистрироваться в db / xml / что угодно.

Если уровень данных действительно должен регистрировать (то есть сообщать о проблеме), чтобы он выдавал исключение, перехватывал его на среднем уровне, а затем решал, как его обработать, или передавал его в класс диагностики и принимал это решение. В любом случае, я бы оставлял DAL настолько «слепым / тупым», насколько это возможно, и не позволял бы ему принимать решения о том, что является и что не является регистрируемым событием.

Общий родительский класс не очень хорошая идея. Регистратор не является базой данных [r]. И при этом база данных не является регистратором. Это не столько проблема курицы и яйца, сколько проблема коровы и свиньи. Это два разных животных. Нет никаких оснований для базы данных быть осведомленным о регистраторе. Я думаю, что вы заставляете абстракцию. Все, что я вижу, - то, что у Регистратора есть База данных .... если это регистратор БД, который является.

Вы все равно управляете слоем данных со среднего уровня, поэтому при включении исключений и событий я не вижу, где вы потеряете верность при регистрации событий, связанных с БД.

 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);
   }
 }
}
3 голосов
/ 08 апреля 2009

Если вы хотите предоставить объекту базы данных доступ к регистратору, то вы передаете ему регистратор. Если вы хотите предоставить вашему регистратору доступ к объекту базы данных, то вы передаете ему объект базы данных.

Проверьте, существуют ли эти объекты в классе, прежде чем использовать функции, предоставленные ими.

Поскольку 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);
1 голос
/ 08 апреля 2009

Создайте класс Logger, который реализует интерфейс ILogger. Новый экземпляр класса Logger получает объект, который реализует интерфейс ILoggingProvider, который используется для вывода сообщений журнала.

Создайте класс Database, который реализует интерфейс ILoggingProvider. Новый экземпляр базы данных получает объект, который реализует интерфейс 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
   }
}
0 голосов
/ 08 апреля 2009

Как бы я это сделал:

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. Здесь предполагается, что ваш код не заботится о том, как он регистрируется, это зависит от администратора, и что все журналирование будет выполняться в одном направлении во время выполнения (что в любом случае будет моим предпочтением). Очевидно, вы могли бы расширить это, чтобы сделать выбор цели журнала при создании регистратора и создать соответствующего поставщика.

0 голосов
/ 08 апреля 2009

Вы можете сделать второй аргумент для функции Database::insert():

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

И затем, очевидно, когда регистратор вызывает его, сделайте второй аргумент ложным.

Либо просто проверьте стек функций, чтобы узнать, была ли вызвана функция вставки из класса Logging.

function insert($sql) {
    $callStack = debug_backtrace();
    if (!isset($callStack[1]) || $callStack[1]['class'] !== "Logger") {
        Logger::logSQL($sql);
    }
    // ...
}
0 голосов
/ 08 апреля 2009

звучит так, будто вы добавляете много сложностей к простой задаче просто ради того, чтобы сделать ее объектно-ориентированной ...

спросите себя, что вы на самом деле получаете, используя этот подход?

0 голосов
/ 08 апреля 2009

ИМХО, класс базы данных не должен взаимодействовать с регистратором. Я был бы склонен вызывать регистратор из той же части кода, которая вызывает класс базы данных. Затем регистратор может использовать класс базы данных для вставки.

0 голосов
/ 08 апреля 2009

Вы можете добавить еще один класс, который будут использоваться как в базе данных, так и в Logger, чтобы избежать бесконечного цикла регистрации (и избежать круговой зависимости).

// 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 реализовали общий интерфейс, а также использовали фабрику для предоставления соответствующего экземпляра.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...