I mixin creano accoppiamento con Ruby?

3

Diciamo che ho la seguente architettura:

module Keyring
    @keyring = Keyring.new
    class Keyring
        def key
        def add_key
        def update_key
        def remove_key
    class KeyStorage
    class Encryptor
        class AesEncryptor
class Connection
    def id
    def user_id
    def protocol
    def name
class FtpConnection
    include Keyring
    def host
    def port
    def username_encryption_id
    def passsword_encryption_id
    def is_passive
    def username
        # Return the decrypted value given an encryption key id.
        return @keyring.key(self.username_encryption_id)
    def password
        # Return the decrypted value given an encryption key id.
        return @keyring.key(self.password_encryption_id)

Come vedi, FtpConnetion usa il Portachiavi.

Ora, non è il Portachiavi accoppiato al modulo Portachiavi (per nome)? Se voglio usare FtpConnection in un altro progetto, dovrei portare con te il portachiavi o implementare un altro modulo con esattamente quel nome, giusto? Non è questo cattivo design?

    
posta Chad Johnson 25.07.2011 - 20:05
fonte

3 risposte

1

Penso che ti sia sfuggito il punto di un mixin qui.

Un mixin in Ruby è solitamente un Modulo con un numero di metodi in (nessuna classe) che possono essere inclusi in qualsiasi classe, per estenderne la funzionalità.

Ad esempio, se tu avessi

module Keyring
    def key
    def add_key
    def update_key
    def remove_key

quindi potresti includerlo in FtpConnection, che otterrebbe i quattro metodi sopra definiti.

Ecco un buon articolo sulla scrittura di mixin.

Quello che stai cercando di fare qui è incapsulamento . Per fare ciò, devi semplicemente spostare il campo @keyring nella classe FtpConnection e rimuovere l'inclusione.

module Keyring
    class Keyring
        def key
        def add_key
        def update_key
        def remove_key
    class KeyStorage
    class Encryptor
        class AesEncryptor
class Connection
    def id
    def user_id
    def protocol
    def name
class FtpConnection
    @keyring = Keyring.new
    def host
    def port
    def username_encryption_id
    def passsword_encryption_id
    def is_passive
    def username
        # Return the decrypted value given an encryption key id.
        return @keyring.key(self.username_encryption_id)
    def password
        # Return the decrypted value given an encryption key id.
        return @keyring.key(self.password_encryption_id)

Ora questo fa crea un accoppiamento stretto tra le due classi, quindi dovresti probabilmente passare il portachiavi in un costruttore (iniezione di dipendenza).

    
risposta data 25.07.2011 - 22:14
fonte
1

Se questo tipo di accoppiamento di nomi dovesse essere considerato come una cattiva progettazione, allora Rails sarebbe un pessimo software e dovremmo rinunciare a tutta l'idea dell'ereditarietà. Ogni parte del software dipende dalle classi della biblioteca e hanno sempre nomi che sono costanti dal momento in cui li includi e li usi. Diciamo che scrivo qualcosa con la libreria Qt, quindi avrò tutto "accoppiato" a cose come QCheckBox. O se utilizzo Rails, non riesco ad andare in giro usando ActiveRecord e, se mi piacerebbe sostituirlo, dovrei riscrivere il tutto con gli stessi nomi per classi e metodi.

Hai un modulo con un'interfaccia chiaramente progettata. La classe FtpConnection non ha bisogno di sapere nulla sullo stato interno di Portachiavi o accedere a nessuno dei suoi dati interni. Finché si trova in un file a sé stante, può essere sostituito in qualsiasi momento da un altro modulo con lo stesso nome e interfaccia.

Ma riconsidererei la dichiarazione e l'uso della variabile @keyring come una specie di variabile globale nel modulo stesso. Finché non è necessario che diverse classi condividano una singola istanza, questa deve essere dichiarata in modo indipendente all'interno di FtpConnection.

Modifica

Se hai bisogno di una singola istanza, creo probabilmente un modulo Application centrale (come il modulo Rails in Ruby on Rails) che lo tiene come variabile globale sotto forma di una variabile di istanza di classe, qualcosa sulla linea

module App
  class << self
    def keyring
      @@keyring ||= Keyring.new
    end
  end
end

E il pdr ha ragione, naturalmente, che quello che hai qui non è un mixin. I mixin funzionerebbero in un modo diverso. Accedono ai metodi definiti nella classe che li include. In un certo senso, dal momento che è il modo in cui Ruby implementa qualcosa di simile all'ereditarietà multipla (ma in qualche modo più potente), si può dire che sono una super classe che accede alla funzionalità nelle sue classi figlio, che è un grosso problema di accoppiamento. Ma come detto sopra, non dovresti preoccuparti dell'accoppiamento all'interno delle strutture ereditarie. Penso che questo termine si riferisca più a moduli più grandi di un progetto che dovrebbe rimanere indipendente o richiedere opzioni molto flessibili per la sostituzione come i driver del database. Ma anche lì dipendi ancora dai nomi di classi e moduli che effettivamente implementano questo comportamento.

Modifica

Lo richiederebbe solo nella parte principale del file prima della classe FtpConnection effettiva e quindi l'accesso ad App.keyring.

Fai attenzione a non confondere require e include. È necessario assicurarsi che il modulo venga caricato una sola volta. Includi lo caricherebbe tutte le volte che lo si utilizza. Questo è principalmente usato con i mixin (ma come detto dal pdr, qui non hai un mixin)

require 'app.rb'

class FtpConnection
  def username
    return App.keyring.key(self.username_encryption_id)
  end
end

Modifica

Sì, in qualche modo è così. Tuttavia, se la tua libreria, app, classe o qualsiasi altra cosa usa un hash, allora è accoppiato a un'implementazione di una classe di hash. O qualcosa di simile, deve almeno implementare qualcosa con lo stesso nome e interfaccia. È così che funziona il software modulare. Se continui con il tuo progetto, molto probabilmente utilizzerai il modulo di Ruby's Net per implementare la funzionalità ftp. quindi sei più o meno costretto ad avere una riga come

require 'net/ftp'

Da quel momento in poi sarai obbligato ad avere questo modulo disponibile o uno simile. Se scrivi un'app che utilizza FtpConnection, lo stesso avverrà o questa classe. Nessuna via d'uscita. Non proprio in ogni caso, puoi avere solo più livelli di astrazione, ma in questo caso è inutile.

A seconda delle tue esigenze puoi ripetere lo stesso schema. Se si riutilizza FtpConnection, è necessario disporre del modulo App. Sebbene tu possa ovviamente fare lo stesso trucco, ma tieni il portachiavi nel modulo Il portachiavi stesso.

Oppure raggruppate tutte le classi che hanno bisogno del portachiavi in un altro modulo. In questo modo potresti avere almeno l'accesso centrale in un unico posto. Supponi di voler riutilizzare FtpConnection, ma questa volta usa un altro portachiavi chiamato KeySomething:

module App
  class << self
    def keyring
      @@keyring ||= KeySomething.new
    end
  end
end

Finito con un cambio di codice. Lo stesso vale per qualsiasi tipo di inizializzazione che potrebbe richiedere il portachiavi. Anche se ovviamente il nuovo portachiavi deve ancora implementare tutti i metodi che usi nel tuo codice.

    
risposta data 25.07.2011 - 21:46
fonte
0

In generale, sì. Tuttavia, non preoccuparti di diventare troppo fanatico per quanto riguarda l'aderenza al design perfetto (ma in generale cerca di farlo). Se devi solo fare un po 'di copia-pasta, va bene. Non lo dirò.

    
risposta data 25.07.2011 - 20:33
fonte

Leggi altre domande sui tag