Best practice: separazione delle preoccupazioni e problemi di ereditarietà

2

Ecco la situazione: Ho un assembly "comune" di accesso ai dati che contiene classi utilizzate in tutti i miei progetti. Alcuni di questi sono classi astratte che vengono implementate solo dai miei livelli di accesso ai dati per ciascun progetto.

Nei miei progetti ho un approccio a più livelli: accesso ai dati separato, livello aziendale e interfaccia utente. Le mie classi di accesso ai dati possono ereditare dalle classi astratte in Comune. Queste classi astratte contengono un metodo "execute".

Nel mio livello aziendale del progetto faccio riferimento solo al livello di accesso ai dati del progetto - Non faccio riferimento ad altri progetti o all'assemblea comune. Ma una volta che ho messo le mie classi astratte che sono sempre riutilizzate in Common, il mio livello aziendale non può più chiamare il metodo "Execute" senza avere un riferimento al comune.

Spero che non sia troppo confuso.

Se non voglio molte interdipendenze tra gli assembly, ho bisogno di spostare le classi astratte nel livello di accesso ai dati di ogni progetto. Ma poi ho ripetuto il codice e il comportamento potenzialmente incoerente tra i progetti.

Ma se continuo così com'è, tutti i miei livelli aziendali devono essere in grado di accedere a questo assembly di accesso ai dati comune, che sembra sbagliato.

Qualche idea su questa architettura?

So che alcuni potrebbero provare a dire "usa Entity Framework" o qualche altro ORM. Ma i miei progetti non sono abbastanza complessi da garantire un sovraccarico, soprattutto considerando la necessità di prestazioni veloci. Ho scoperto che un semplice framework di mia proprietà che implementa direttamente ADO.Net è notevolmente più veloce. Quindi, per favore, basta avvisarmi sulla separazione dei problemi di preoccupazione e ereditarietà e non cercare di convincermi ad aggiungere un ORM.

Codice di esempio:

In Common: classe base

Public MustInherit Class AbstractDatabaseAction
    Protected Property Factory As DbProviderFactory
    Protected Property Connection As DbConnection
    Protected Property Command As DbCommand
    Protected Property MessageForExceptions As String
    Protected Property ProviderName As String

    Protected Sub New(connString As String, providerName As String, messageForExceptions As String)
        Factory = DbProviderFactories.GetFactory(providerName)

        'set up connection
        Connection = Factory.CreateConnection
        Connection.ConnectionString = connString
        Me.ProviderName = providerName
        'set up command
        Command = Factory.CreateCommand

        Me.MessageForExceptions = messageForExceptions
    End Sub

    Public MustOverride Sub Execute()
    Protected Overridable Sub SetParameters()
        'nothing
    End Sub
    Protected MustOverride Sub SetCommandText()
    Protected Overridable Sub SetCommandType()
        Command.CommandType = CommandType.StoredProcedure
    End Sub

    Protected Sub BuildCommand()
        Command.Connection = Connection
        Me.SetCommandText()
        Me.SetCommandType()
        Me.SetParameters()

        If Me.ProviderName = "Oracle.DataAccess.Client" Then
            OracleSpecificCommandEdits()
        End If
    End Sub

    Protected Overridable Sub OracleSpecificCommandEdits()
        CType(Command, OracleCommand).BindByName = True
    End Sub


End Class

In Common: seconda classe di base (ho sia una ricerca che una versione di salvataggio, con la versione di salvataggio che consente facoltativamente le transazioni.)

Public MustInherit Class AbstractSearch
    Inherits AbstractDatabaseAction

    Protected Sub New(connString As String, providerName As String, messageForExceptions As String)
        MyBase.New(connString, providerName, messageForExceptions)
    End Sub

    Public Overrides Sub Execute()
        Try
            Me.BuildCommand()

            Using Connection
                Connection.Open()

                Using Command
                    Try
                        Dim rdr As IDataReader = Command.ExecuteReader
                        Me.fill(rdr)
                        rdr.Close()
                    Catch ex As Exception
                        Throw New Exception(MessageForExceptions & "->Search", ex)
                    End Try
                End Using
            End Using
        Catch ex As Exception
            Throw New Exception(MessageForExceptions & "->Search", ex)
        End Try
    End Sub

    Protected MustOverride Sub fill(ByRef rdr As System.Data.IDataReader)

    Protected Overrides Sub OracleSpecificCommandEdits()
        MyBase.OracleSpecificCommandEdits()

        If TypeOf (Factory) Is OracleClientFactory Then
            Dim p As DbParameter = New OracleParameter
            p.ParameterName = "results"
            p.Direction = ParameterDirection.Output
            CType(p, OracleParameter).OracleDbType = OracleDbType.RefCursor
            Command.Parameters.Add(p)
        End If
    End Sub

Protected Sub AddInParameter(key As String, value As Object)
    Dim p As IDataParameter = Command.CreateParameter
    p.Direction = ParameterDirection.Input
    p.Value = value
    p.ParameterName = key
    Command.Parameters.Add(p)
End Sub

Protected Sub AddOutParameter(key As String, type As System.Data.DbType)
    Dim p As IDataParameter = Command.CreateParameter
    p.Direction = ParameterDirection.Output
    p.DbType = type
    p.ParameterName = key
    Command.Parameters.Add(p)
End Sub

End Class

Un esempio di implementazione molto semplice di un'implementazione del livello di accesso ai dati:

Public Class IpBlackListSearch
        Inherits Common.DataAccess.AbstractSearch

        Private Property IPToSearch As String
        Public Property Results As List(Of String) = Nothing

        Public Sub New(connString As String, providerName As String, ipAddressToSearch As String)
            MyBase.New(connString, providerName, "IpAddressSearch")
            Me.IPToSearch = IPToSearch
        End Sub

        Protected Overrides Sub fill(ByRef rdr As System.Data.IDataReader)
            Results = New List(Of String)

            While rdr.Read
                Results.Add(HelperFunctions.NullScrubber(Of String)("ip"))
            End While

        End Sub

        Protected Overrides Sub SetCommandText()
            Command.CommandText = "Get_IPBlacklist"
        End Sub

        Protected Overrides Sub SetParameters()
            MyBase.AddInParameter("in_ip", Me.IPToSearch)
        End Sub
    End Class

Il problema sarebbe venuto quando nel livello aziendale del mio progetto avrei fatto qualcosa di simile:

Dim srch as new IpBlackListSearch(connstring, providername, "12.12.12.12.")
srch.execute

srch.Execute può essere compilato solo se il livello aziendale fa riferimento all'assembly di accesso ai dati comune.

Sembra dai commenti che non c'è niente di sbagliato nel mio livello aziendale contenente quel riferimento.

    
posta user158017 07.04.2014 - 17:08
fonte

3 risposte

1

Da quanto ho capito la tua domanda, sembra che le tue classi di livello di accesso ai dati stessero accedendo all'assemblea comune e che il tuo livello aziendale abbia avuto accesso al tuo livello di accesso ai dati specifici del progetto.

Ora vuoi inserire alcune delle classi del livello di accesso ai dati comuni nell'Assemblea comune. Pensi che non sia più OK per il Business Layer accedere al DAL. Tuttavia, il livello aziendale stava già accedendo al DAL. Quindi, in realtà, cosa c'è di diverso?

Se vuoi solo esporre le tue classi astratte, allora sembra che debbano andare nel loro comune assemblaggio DAL.

    
risposta data 07.04.2014 - 18:51
fonte
3

Considera l'utilizzo di delegati invece di classi astratte. Il pattern Method Method è principalmente un trucco per aggirare la mancanza di funzioni di prima classe da parte di una lingua. Quindi, anziché eseguire l'override di "execute", passalo come argomento. A parte questo, perché non vuoi le dipendenze tra le assemblee? A che serve la tua assemblea comune se non la userai?

    
risposta data 07.04.2014 - 17:21
fonte
2

Questo esatto problema è il motivo per cui esiste Principio di inversione delle dipendenze . Questo principio dice.

  • I moduli di alto livello non dovrebbero dipendere da moduli di basso livello. Entrambi dovrebbero dipendere dalle astrazioni.
  • Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.

Nel tuo caso, il livello aziendale dipende dal dettaglio che è l'accesso ai dati. Per risolvere questo problema, è necessario creare un'astrazione che faccia parte del livello aziendale indicante il tipo di operazioni sui dati necessarie al livello aziendale. Il livello di accesso ai dati realizza quindi questa astrazione mentre eredita dalla classe common / base di accesso ai dati. Questa implementazione concreta viene quindi creata utilizzando una sorta di meccanismo di fabbrica, come modello Abstract Factory o utilizzando Iniezione di dipendenza .

    
risposta data 07.04.2014 - 18:23
fonte

Leggi altre domande sui tag