2009-04-07 58 views
1

我有一個雞蛋問題。我也想用OOP方式在PHP中實現一個系統,其中兩個類將扮演重要的角色:數據庫和日誌。我的想法是建立由數據庫類的連接,這將有公共方法,例如。 runQuery(sUpdateQuery),doInsert(sInsert)等.Log類將通過通用方法寫入日誌,就像logMessage(message),logDatabaseQuery(sQuery)TO THE DATABASE一樣。現在的問題出現了。面向對象方式的雞蛋問題

1:數據庫裏面的類的方法,我想能夠使用Log類的logDatabaseQuery(sQuery)

2:這仍然不會是一個很大的挑戰,如果我不喜歡用logDatabaseQuery方法內的數據庫類的doInsert(sInsert)方法。

我想保持它簡單 - 並且只使用數據庫連接對象的一個​​實例,並且如果可能的話,使用loggeras。

對於很多人來說Singleton模型將是第一個想要選擇的,但我絕對想用不同的解決方案。

所以會有兩班它將使用各-其他的方法:

數據庫 doInsert logDatabaseQuery

登錄 logDatabaseQuery doInsert

我想單獨保留日誌的方法(在Log類中),因爲稍後會有其他方法來記錄數據庫,而不僅僅是文件或電子郵件。

任何想法,這應該/如何以最好的,面向對象的方式完成?

我在思考一個共同的父抽象類,或關於使用接口以及,但最後還是沒能找出正確的方法:(

我想知道的是一個正確的類層次結構的建議

+0

夥計們,謝謝你們所有的答案。現在我可以清楚地看到 - 我有點害怕,我的想法是錯誤的。現在我可以理解,我必須將數據庫訪問分爲至少兩個類。 – simply4it 2009-04-08 23:08:58

回答

4

你已經把太多的東西結合在了一起。

數據庫不能真正依賴於它取決於數據庫的記錄器。這不是好的設計。

你真的有什麼有兩種數據庫訪問。

低級別的訪問,那麼「原始」 SQL。

記錄器可以依賴於這一水平一流。它本身不具有原始SQL。這取決於較低級的課程。

高級訪問沒有應用程序查詢,它使用低級訪問記錄器。

+0

將「原始」SQL放入日誌記錄類是一個很好的方式,可以讓你以後必須清理一團糟。只需將它傳遞給數據庫對象並將數據庫對象傳遞給它即可。 – 2009-04-07 21:50:25

0

創建插入方法重載,允許在不記錄插入,和你的日誌類使用,否則,你的設計是通過定義遞歸:可以通過執行DB插入記錄所有DB插入

0

添加可選參數爲doInsert $ callerIsLogger = false,然後每次您需要從記錄器中執行插入(),提供第二個參數true,並且您的doInsert可以檢查此情況,並在記錄器調用記錄器時不調用記錄器。什麼階層? KISS的方法岩石:)

0

這是一個很好的人選Mediator Pattern。您將擁有一個對象,即記錄器和數據庫都會調用各種方法,並且此中介對象將保持對記錄器和數據庫的引用,併爲您處理通信。

1

創建一個實現接口ILogger的Logger類。 Logger類的新實例接收一個Object,該Object實現用於輸出日誌消息的ILoggingProvider接口。

創建一個實現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

這不會給你帶來遞歸問題嗎? – 2009-04-07 21:37:22

+0

這正是S.Lott指出的。你不能在數據庫類的任何地方使用日誌記錄 - 所以明確地分離這些部分是很好的。 – 2009-04-07 21:45:25

4

這聽起來像你有兩個不同的數據庫訪問 - 記錄(正常情況下)和未記錄(用於記錄功能)。將數據庫類拆分爲兩個版本,一個是記錄請求的較高版本,另一個是未記錄請求的較低版本。使用對日誌和較低級別數據庫類的引用來實現更高級別的數據庫類。

+0

您可以編寫數據庫類,以便在記錄器對象可用時記錄活動,並在記錄器對象不可用時不會記錄活動。編寫兩個數據庫類是沒有必要的。 – 2009-04-08 00:08:06

+0

當然,你可以這樣做。但是這個問題需要一個面向對象的解決方案來重用數據庫對象的一個​​實例,我認爲這是最簡單的方法。 – 2009-04-08 02:27:50

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實現一個通用接口來修飾它,並使用工廠提供相應的實例。

3

如果你想給一個記錄數據庫對象的訪問,則通過記錄到它。如果您想讓記錄器訪問數據庫對象,那麼您將數據庫對象傳遞給它。

在使用由它們提供的函數之前,請檢查這些對象是否存在於類中。

由於PHP默認通過引用傳遞這些對象,因此可以使用單個記錄器和數據庫類爲幾個對象執行此操作。

我還建議你在記錄器中的單點處寫所有的數據庫。只需將所有內容存儲在臨時陣列中,並在解構時運行DB代碼。否則,如果您在整個代碼中寫入數據庫,則會產生大量開銷。

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);
0

恕我直言,數據庫類不應該與記錄器交互。我傾向於從調用數據庫類的代碼的相同部分調用記錄器。然後記錄器可以使用數據庫類來進行插入。

3

從記錄器中解耦數據庫。我將設計應用程序從中間層進行登錄,並決定在運行時使用哪種記錄器類型,而不是編譯時間。即使用工廠方法來確定是否要記錄到db/xml/whatever。

如果數據層確實需要記錄(即,報告問題)是否會拋出異常,將其捕獲到中間層中,然後決定如何處理它,或將它交給診斷類並做出決定。無論哪種方式,我都會盡可能保持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); 
    } 
} 
} 
0

聽起來像是你使其面向對象的緣故增加了許多困難,以一個簡單的問題純粹...

問問自己,什麼是你真的採取這種做法獲得?

0

你可以做第二ARG爲您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

我會怎麼做:

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類硬編碼他們。這裏的假設是你的代碼不關心它是如何記錄的,這是管理員的職責,並且所有的日誌記錄都將在執行期間以單向方式完成(無論如何我都會這樣做)。很明顯,您可以擴展此選項以在創建記錄器時創建日誌目標並創建適當的提供者。