2009-04-07 6 views
1

Ich habe ein Huhn-Ei-Problem. Ich möchte auch ein System in PHP in einer OOP-Weise implementieren, in der zwei Klassen eine wichtige Rolle spielen: Datenbank und Protokoll. Meine Idee war, die Verbindung durch die Datenbank-Klasse aufzubauen, die öffentliche Methoden haben würde, z. runQuery (sUpdateQuery), doInsert (sInsert) usw. Die Log-Klasse schreibt Protokolle über gängige Methoden genauso wie logMessage (message), logDatabaseQuery (sQuery) TO DATABASE. Das Problem kommt jetzt.Egg-Huhn Problem in OOP Weg

1: Innerhalb der Methoden der Klasse des Datenbank würde ich gerne der Lage sein, die logDatabaseQuery (sQuery) Logbuch Klasse

2 zu verwenden: wäre dies noch nicht eine große Herausforderung sein, wenn ich nicht das verwenden möchten Die doInsert-Methode (sInsert) der Datenbankklasse innerhalb der logDatabaseQuery-Methode.

Ich möchte es einfach halten - und nur eine Instanz des Datenbankverbindungsobjekts verwenden, und von den Loggeras gut, wenn es möglich ist.

Für viele Menschen wäre Singleton Modell die erste Idee zu wählen, aber ich würde definitiv gerne andere Lösung verwenden.

So würde es zwei Klassen, die jeweils-andere Methoden verwenden würden:

Datenbank doInsert logDatabaseQuery

Log logDatabaseQuery doInsert

Ich mag würde separat die Log Methoden halten (in der Log-Klasse), da es später andere Methoden zur Protokollierung nicht nur in der Datenbank, sondern auch in Dateien oder E-Mails geben würde.

Irgendwelche Ideen, wie dies auf die schönste, OOP-freundliche Art und Weise geschehen sollte?

Ich war um eine gemeinsame übergeordnete abstrakte Klasse zu denken, oder über Schnittstellen als auch verwenden, aber schließlich nicht die richtige Art und Weise herausfinden konnte :(

Was würde ich ein Vorschlag für eine korrekte Klassenhierarchie weiß, ist

+0

Jungs, vielen Dank für Ihre Antworten. Jetzt kann ich deutlich sehen - wovor ich etwas Angst hatte, dass meine Idee falsch war. Jetzt kann ich verstehen, dass ich den Datenbankzugriff zumindest in zwei Klassen trennen muss. – simply4it

Antwort

4

Sie haben zu viele Dinge miteinander kombiniert.

Die Datenbank kann nicht wirklich von einem Logger abhängig sein, der von der Datenbank abhängt. Es ist kein gutes Design.

Was Sie wirklich haben, sind zwei Arten von Datenbankzugriff.

Low-Level-Zugriff führt "rohen" SQL.

Der Logger kann von dieser untergeordneten Klasse abhängen. Es enthält selbst kein SQL. Es hängt von einer untergeordneten Klasse ab.

High-Level-Zugriff führt Anwendungsabfragen durch, verwendet Low-Level-Zugriff und der Logger.

+0

Wenn Sie "rohen" SQL in Ihre Logger-Klasse einfügen, ist das ein guter Weg, um ein Durcheinander zu schaffen, das Sie später aufräumen müssen. Übergeben Sie es einfach an ein Datenbankobjekt und übergeben Sie das Datenbankobjekt an es. –

+0

gibt es keine Anforderung, dass die untergeordnete DB nur Roh-SQL ausführen. Wenn Sie möchten, erstellen Sie eine zusätzliche Schicht: 1: Raw SQL, 2: OOP DB (verwendet 1), Logger (verwendet 2), 3: geloggte DB (verwendet 2 und Logger) – Javier

0

erstellen ein Insert -Methodenüberladung, die für Einsätze ohne Protokollierung ermöglicht, und Ihre Logging-Klasse verwenden, die andernfalls Ihr Design durch Definition rekursiv ist:.. Melden Sie alle DB-Einsätze durch einen DB Einsatz Durchführung

0

optional hinzufügen Parameter zu doInsert $ callerIsLogger = false, dann jedes Mal Sie müssen doInsert() von Ihrem Logger eingeben, den zweiten Parameter true bereitstellen, und Ihr doInsert konnte diese Situation überprüfen und den Logger nicht aufrufen, wenn er vom Logger aufgerufen wird. Welche Hierarchien? KISS Methodik rockt :)

0

Dies ist ein guter Kandidat für die Mediator Pattern. Sie hätten ein Objekt, auf das der Logger und die Datenbank beide Methoden anwenden würden, und dieses Mediatorobjekt würde Referenzen auf den Logger und die Datenbank behalten und die Kommunikation für Sie übernehmen.

1

Erstellen Sie eine Logger-Klasse, die eine Schnittstelle ILogger implementiert. Eine neue Instanz der Logger-Klasse empfängt ein Objekt, das die Schnittstelle ILoggingProvider implementiert, die zum Ausgeben von Protokollnachrichten verwendet wird.

Erstellen Sie eine Datenbankklasse, die die ILoggingProvider-Schnittstelle implementiert. Eine neue Instanz der Datenbank empfängt ein Objekt, das die ILogger-Schnittstelle implementiert, die zum Protokollieren von Nachrichten verwendet wird.

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

Bleibt das nicht bei einem Rekursionsproblem? –

+0

Genau darauf hat S.Lott hingewiesen. Sie können die Protokollierung nicht überall in der Database-Klasse verwenden. Daher ist es ratsam, diese Teile explizit zu trennen. –

4

Es klingt wie Sie zwei verschiedene Datenbankzugriffe - angemeldet (Normalfall) und unlogged (für die Logging-Funktionen). Teilen Sie die Datenbankklasse in zwei Teile auf, eine höhere Version für protokollierte Anforderungen und eine niedrigere Version für nicht protokollierte Anforderungen. Implementieren Sie die Datenbankklasse der höheren Ebene mithilfe von Verweisen auf die Datenbankklassen der Protokollierung und der untergeordneten Ebene.

+0

Sie können die Datenbankklasse schreiben, sodass sie Aktivitäten protokollieren kann, wenn ein Protokollierungsobjekt verfügbar ist, und Aktivitäten nicht protokolliert werden, wenn ein Protokollierungsobjekt nicht verfügbar ist. Das Schreiben von zwei Datenbankklassen ist nicht notwendig. –

+0

Sicher, du könntest es so machen. Aber die Frage nach einer objektorientierten Lösung, die eine Instanz eines Datenbankobjekts wiederverwendet, und ich denke, dies ist der sauberste Weg, dies zu tun. –

0

Sie könnten eine weitere Klasse hinzufügen, die sowohl die Datenbank als auch der Logger verwenden, um zu vermeiden, in eine unendliche Logging-Schleife zu gelangen (und auch die zirkuläre Abhängigkeit zu vermeiden).

// 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); 
    } 
} 

Man könnte es verkleiden sich ein wenig von beiden mit Datenbank und DatabaseProxy sowie eine gemeinsame Schnittstelle implementieren, und eine Fabrik verwenden Sie die entsprechende Instanz zur Verfügung zu stellen.

3

Wenn Sie dem Datenbankobjekt Zugriff auf einen Logger gewähren möchten, übergeben Sie den Logger an diesen Logger. Wenn Sie Ihrem Logger Zugriff auf ein Datenbankobjekt gewähren möchten, übergeben Sie das Datenbankobjekt an dieses Objekt.

Überprüfen Sie, ob diese Objekte in der Klasse vorhanden sind, bevor Sie Funktionen verwenden, die von ihnen bereitgestellt werden.

Da PHP diese Objekte standardmäßig per Referenz übergibt, können Sie dies mit einer einzelnen Logger- und Datenbankklasse für mehrere Objekte tun.

Ich würde auch empfehlen, dass Sie alle Ihre Datenbank schreiben an einem einzigen Punkt im Logger. Speichern Sie einfach alles in einem temporären Array und führen Sie Ihren DB-Code beim Dekonstruieren aus. Andernfalls erzeugen Sie eine Menge Overhead, wenn Sie im gesamten Code in die Datenbank schreiben.

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

IMHO sollte die Datenbank-Klasse nicht mit dem Logger interagiert werden. Ich würde geneigt sein, den Logger aus demselben Teil des Codes aufzurufen, der die Datenbankklasse aufruft. Dann kann der Logger die Datenbankklasse verwenden, um seine Inserts auszuführen.

3

Datenbank vom Logger entkoppeln. Ich würde die Anwendung entwerfen, um von der mittleren Schicht zu protokollieren und die Entscheidung zu treffen, welcher Loggertyp bei Laufzeit nicht kompiliert werden soll. I.e. Verwenden Sie eine Factory-Methode, um zu bestimmen, ob in db/xml/was auch immer protokolliert werden soll.

Wenn der Data Layer protokolliert werden muss (d. H.Melden Sie ein Problem) Lassen Sie eine Ausnahme auslösen, fangen Sie sie in der mittleren Ebene ab und entscheiden Sie dann, wie sie dort behandelt wird oder übergeben Sie sie an eine Diagnoseklasse und entscheiden Sie sich dafür. So oder so würde ich die DAL so "blind/dumm" wie möglich halten und keine Entscheidungen darüber treffen, was ein logbares Ereignis ist und was nicht.

Eine gemeinsame Elternklasse ist keine gute Idee. Ein Logger ist keine Datenbank [r]. Und noch ist eine Datenbank ein Logger. Es ist nicht so sehr ein Problem mit Huhn und Ei, sondern ein Problem mit Kuh und Schwein. Sie sind zwei verschiedene Tiere. Es gibt keinen Grund dafür, dass die Datenbank den Logger kennt. Ich denke, du zwingst die Abstraktion. Alles, was ich sehe, ist, dass ein Logger eine Datenbank hat ... wenn es ein db-Logger ist.

Sie fahren die Datenschicht trotzdem von der mittleren Schicht, wenn ich also Ausnahmen und Ereignisse einschließe, kann ich nicht sehen, wo Sie die Genauigkeit bei der Protokollierung db-bezogener Ereignisse verlieren würden.

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

klingt wie Sie es viel Komplikation für ein einfaches Problem rein aus Gründen der Herstellung objektorientiert ...

fragen Sie sich selbst, was Sie wirklich zu gewinnen, indem man diesen Ansatz Zugabe?

+0

so viel wie ich hasse "OOP für den Willen von OOP "(oder irgendein Design-Stil" nur weil "), in diesem speziellen Fall ist es kein schlechtes Design, und gut gemacht kann viel wartungsfreundlicher sein als nicht-OOP sequentieller Code (gibt es einen Namen dafür?Nein, 'Imperativ' ist nicht) – Javier

+0

'Procedural', denke ich, ist das Wort –

+0

Ich stimme nicht zu, er braucht ein einzelnes Schnittstellenmodul für eine einzelne Datenbank und ein einzelnes Protokollmodul. Immer wenn der Begriff Singleton ins Spiel kommt, haben Sie zugegeben, dass ein OOP-Ansatz das Problem nicht lösen konnte. wenn er mehrere Log-Typ-Module oder mehrere Datenbank-Back-Ends hatte, dann war das OOP der ganze Weg. –

0

Sie könnten einen zweiten arg für Ihre Database::insert() Funktion machen:

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

Und dann offensichtlich, wenn der Logger es nennt, das zweite Argument falsch machen.

Alternativ können Sie auch den Funktions-Stack überprüfen, um zu sehen, ob die Insert-Funktion aus der Logging-Klasse aufgerufen wurde.

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

Wie ich es tun würde:

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(); 
    } 
} 

Obwohl ich würde wahrscheinlich auch eine Art aus einer Konfigurationsdatei nachschlagen machen, anstatt sie in der ProviderFactory Klasse zu. Die Annahme hier ist, dass es Ihrem Code egal ist, wie er protokolliert wird, das ist der Administrator, und dass die gesamte Protokollierung in einer Richtung während der Ausführung erfolgen würde (was meine Präferenz sowieso wäre). Natürlich können Sie dies erweitern, um beim Erstellen des Loggers eine Auswahl bezüglich des Log-Ziels zu treffen und den entsprechenden Provider zu erstellen.

+0

Gleiches Rekursionsproblem. Ich habe fast einen Kommentar oben beantwortet. Ja, Sie müssen in einen nicht protokollierenden DbProvider trennen, der nur den Datenbankzugriff durchführt. –