iXdi - Σύγχρονος προγραμματισμός

Πώς να εφαρμόσετε μια αφηρημένη τάξη στο Go;

Πώς να εφαρμόσετε μια αφηρημένη τάξη στο Go; Καθώς το Go δεν μας επιτρέπει να έχουμε πεδία στις διεπαφές, αυτό θα ήταν ένα αντικείμενο χωρίς κατάσταση. Έτσι, με άλλα λόγια, είναι δυνατόν να υπάρχει κάποιο είδος προεπιλεγμένης υλοποίησης για μια μέθοδο στο Go;

Εξετάστε ένα παράδειγμα:

type Daemon interface {
    start(time.Duration)
    doWork()
}

func (daemon *Daemon) start(duration time.Duration) {
    ticker := time.NewTicker(duration)

    // this will call daemon.doWork() periodically  
    go func() {
        for {
            <- ticker.C
            daemon.doWork()
        }
    }()
}

type ConcreteDaemonA struct { foo int }
type ConcreteDaemonB struct { bar int }

func (daemon *ConcreteDaemonA) doWork() {
    daemon.foo++
    fmt.Println("A: ", daemon.foo)
}

func (daemon *ConcreteDaemonB) doWork() {
    daemon.bar--
    fmt.Println("B: ", daemon.bar)
}

func main() {
    dA := new(ConcreteDaemonA)
    dB := new(ConcreteDaemonB)

    start(dA, 1 * time.Second)
    start(dB, 5 * time.Second)

    time.Sleep(100 * time.Second)
}

Αυτό δεν θα μεταγλωττιστεί καθώς δεν είναι δυνατό να χρησιμοποιηθεί η διεπαφή ως δέκτης.

Μάλιστα, έχω ήδη απαντήσει στην ερώτησή μου (δείτε την απάντηση παρακάτω). Ωστόσο, είναι ιδιωματικός τρόπος να εφαρμοστεί μια τέτοια λογική; Υπάρχουν λόγοι για να μην υπάρχει προεπιλεγμένη υλοποίηση εκτός από την απλότητα της γλώσσας;

15.05.2015

Απαντήσεις:


1

Εάν θέλετε να παρέχετε μια "προεπιλεγμένη" υλοποίηση (για Daemon.start()), αυτό δεν είναι το χαρακτηριστικό μιας διεπαφής (τουλάχιστον όχι στο Go). Αυτό είναι ένα χαρακτηριστικό ενός τύπου concrete (χωρίς διεπαφή).

Επομένως, το Daemon στην περίπτωσή σας θα πρέπει να είναι συγκεκριμένος τύπος, βολικά ένα struct αφού θέλετε να έχει πεδία. Και η εργασία που πρέπει να γίνει μπορεί να είναι είτε μια τιμή ενός τύπου διεπαφής, είτε σε μια απλή περίπτωση απλώς μια τιμή συνάρτησης (μια απλή περίπτωση σημαίνει ότι θα είχε μόνο μία μέθοδο).

Με τύπο διεπαφής

Δοκιμάστε την πλήρη εφαρμογή στο Go Playground.

type Task interface {
    doWork()
}

type Daemon struct {
    task Task
}

func (d *Daemon) start(t time.Duration) {
    ticker := time.NewTicker(t)
    // this will call task.doWork() periodically
    go func() {
        for {
            <-ticker.C
            d.task.doWork()
        }
    }()
}

type MyTask struct{}

func (m MyTask) doWork() {
    fmt.Println("Doing my work")
}

func main() {
    d := Daemon{task: MyTask{}}
    d.start(time.Millisecond*300)

    time.Sleep(time.Second * 2)
}

Με τιμή συνάρτησης

Σε αυτή την απλή περίπτωση αυτή είναι πιο σύντομη. Δοκιμάστε το στο Go Playground.

type Daemon struct {
    task func()
}

func (d *Daemon) start(t time.Duration) {
    ticker := time.NewTicker(t)
    // this will call task() periodically
    go func() {
        for {
            <-ticker.C
            d.task()
        }
    }()
}

func main() {
    d := Daemon{task: func() {
        fmt.Println("Doing my work")
    }}
    d.start(time.Millisecond * 300)

    time.Sleep(time.Second * 2)
}
15.05.2015
  • Η λύση τιμής συνάρτησης είναι μακράν σημασιολογικά πλησιέστερη στην έννοια Java ή C# μιας αφηρημένης κλάσης. Αυτή πρέπει να είναι η αποδεκτή απάντηση. 07.11.2020

  • 2

    Οι άλλες απαντήσεις παρέχουν μια εναλλακτική λύση στο πρόβλημά σας, ωστόσο πρότειναν λύση χωρίς τη χρήση αφηρημένων κλάσεων/δομών και υποθέτω ότι αν σας ενδιέφερε να χρησιμοποιήσετε λύση αφηρημένης κλάσης, εδώ είναι πολύ ακριβής λύση στο πρόβλημά σας:

    Πηγαίνετε στην πλατεία

    package main
    
    import (
        "fmt"
        "time"
    )
    
    type Daemon interface {
        start(time.Duration)
        doWork()
    }
    
    type AbstractDaemon struct {
        Daemon
    }
    
    func (a *AbstractDaemon) start(duration time.Duration) {
        ticker := time.NewTicker(duration)
    
        // this will call daemon.doWork() periodically  
        go func() {
            for {
                <- ticker.C
                a.doWork()
            }
        }()
    }
    
    
    
    type ConcreteDaemonA struct { 
    *AbstractDaemon
    foo int
    }
    
    func newConcreteDaemonA() *ConcreteDaemonA {
      a:=&AbstractDaemon{}
      r:=&ConcreteDaemonA{a, 0}
      a.Daemon = r
      return r
    }
    
    
    type ConcreteDaemonB struct { 
    *AbstractDaemon
    bar int
    }
    
    func newConcreteDaemonB() *ConcreteDaemonB {
      a:=&AbstractDaemon{}
      r:=&ConcreteDaemonB{a, 0}
      a.Daemon = r
      return r
    }
    
    
    
    func (a *ConcreteDaemonA) doWork() {
        a.foo++
        fmt.Println("A: ", a.foo)
    }
    
    func (b *ConcreteDaemonB) doWork() {
        b.bar--
        fmt.Println("B: ", b.bar)
    }
    
    
    func main() {
        var dA  Daemon = newConcreteDaemonA()
        var dB  Daemon = newConcreteDaemonB()
    
        dA.start(1 * time.Second)
        dB.start(5 * time.Second)
    
        time.Sleep(100 * time.Second)
    }
    

    Αν αυτό δεν είναι ακόμα προφανές πώς να χρησιμοποιήσετε τις αφηρημένες κλάσεις/πολλαπλή κληρονομικότητα στο go-lang, εδώ είναι η ανάρτηση με αναλυτικές λεπτομέρειες. Μαθήματα περίληψης σε εξέλιξη

    01.09.2016
  • μπορείτε να διορθώσετε την εσοχή στο μπλοκ κωδικών σας; 17.06.2021

  • 3

    Μια εύκολη λύση είναι να μετακινήσετε το daemon *Daemon στη λίστα ορισμάτων (καταργώντας έτσι το start(...) από τη διεπαφή):

    type Daemon interface {
        // start(time.Duration)
        doWork()
    }
    
    func start(daemon Daemon, duration time.Duration) { ... }
    
    func main() {
        ...
        start(dA, 1 * time.Second)
        start(dB, 5 * time.Second)
        ...
    }
    
    15.05.2015
  • Δεν είναι απλά εύκολο, είναι η ιδέα και ο σκοπός των διεπαφών. Θα πρότεινα να κάνετε την απάντησή σας ακόμα πιο ισχυρή. 15.05.2015
  • Αυτό δεν μοιάζει πολύ με την έννοια άλλων γλωσσών για μια αφηρημένη τάξη, αλλά είναι ίσως η πιο ιδιωματική λύση Go (για παράδειγμα, είναι ο τρόπος με τον οποίο λειτουργούν πολλά από τα βασικά API IO). 07.11.2020

  • 4

    Η λύση του Max Malysh θα λειτουργούσε σε ορισμένες περιπτώσεις εάν δεν χρειάζεστε εργοστάσιο. Ωστόσο, η λύση που δόθηκε από τον Adrian Witas θα μπορούσε να προκαλέσει προβλήματα κυκλικών εξαρτήσεων.

    Αυτός είναι ο τρόπος με τον οποίο πέτυχα την εφαρμογή μιας αφηρημένης τάξης με τον εύκολο τρόπο σεβόμενος τις κυκλικές εξαρτήσεις και τα καλά εργοστασιακά μοτίβα.

    Ας υποθέσουμε ότι έχουμε την ακόλουθη δομή πακέτου για το στοιχείο μας

    component
      base
        types.go
        abstract.go
      impl1
        impl.go
      impl2
        impl.go
      types.go
      factory.go
    

    Ορίστε τον ορισμό του στοιχείου, σε αυτό το παράδειγμα θα οριστεί εδώ:

    component/types.go

    package component
    
    type IComponent interface{
        B() int
        A() int
        Sum() int
        Average() int
    }
    

    Τώρα ας υποθέσουμε ότι θέλουμε να δημιουργήσουμε μια αφηρημένη κλάση που να υλοποιεί μόνο το Sum και το Average, αλλά σε αυτήν την αφηρημένη υλοποίηση θα θέλαμε να έχουμε πρόσβαση για να χρησιμοποιήσουμε τις τιμές που επιστρέφονται από το υλοποιημένο A και B

    Για να το πετύχουμε αυτό, θα πρέπει να ορίσουμε μια άλλη διεπαφή για τα αφηρημένα μέλη της αφηρημένης υλοποίησης

    component/base/types.go

    package base
    
    type IAbstractComponentMembers {
        A() int
        B() int
    }
    

    Και τότε μπορούμε να προχωρήσουμε στην υλοποίηση της αφηρημένης «τάξης»

    component/base/abstract.go

    package base
    
    type AbstractComponent struct {
        IAbstractComponentsMember
    }
    
    func (a *AbstractComponent) Sum() int {
        return a.A() + a.B()
    }
    
    func (a *AbstractComponent) Average() int {
        return a.Sum() / 2
    }
    

    Και τώρα προχωράμε στις υλοποιήσεις

    component/impl1/impl.go // Υποθέστε κάτι παρόμοιο για το impl2

    package impl1
    
    type ComponentImpl1 struct {
        base.AbstractComponent
    }
    
    func (c *ComponentImpl1) A() int {
        return 2
    }
    
    func (c *ComponentImpl1) A() int {
        return 4
    }
    
    // Here is how we would build this component
    func New() *ComponentImpl1 {
        impl1 := &ComponentImpl1{}
        abs:=&base.AbstractComponent{
            IAbstractComponentsMember: impl1,
        }
        impl1.AbstractComponent = abs
        return impl1
    }
    

    Ο λόγος που χρησιμοποιούμε μια ξεχωριστή διεπαφή για αυτό αντί να χρησιμοποιήσουμε το παράδειγμα Adrian Witas, είναι επειδή εάν χρησιμοποιήσουμε την ίδια διεπαφή σε αυτήν την περίπτωση, εάν εισαγάγουμε το πακέτο base στο strong>impl* για να χρησιμοποιήσουμε την αφηρημένη "κλάση" και επίσης εισάγουμε τα πακέτα impl* στο πακέτο components, ώστε το εργοστάσιο να τα καταχωρήσει, Θα βρείτε μια κυκλική αναφορά.

    Έτσι θα μπορούσαμε να έχουμε μια εργοστασιακή υλοποίηση όπως αυτή

    component/factory.go

    package component
    
    // Default component implementation to use
    const defaultName = "impl1"
    var instance *Factory
    
    type Factory struct {
        // Map of constructors for the components
        ctors map[string]func() IComponent
    }
    
    func (f *factory) New() IComponent {
        ret, _ := f.Create(defaultName)
        return ret
    }
    
    func (f *factory) Create(name string) (IComponent, error) {
        ctor, ok := f.ctors[name]
        if !ok {
            return nil, errors.New("component not found")
        }
        return ctor(), nil
    }
    
    func (f *factory) Register(name string, constructor func() IComponent) {
        f.ctors[name] = constructor
    }
    
    func Factory() *Factory {
        if instance == nil {
            instance = &factory{ctors: map[string]func() IComponent{}}
        }
        return instance
    }
    
    // Here we register the implementations in the factory
    func init() {
        Factory().Register("impl1", func() IComponent { return impl1.New() })
        Factory().Register("impl2", func() IComponent { return impl2.New() })
    }
    
    29.04.2018
  • @Juan Carlos Diaz παρακαλώ αναλύστε την αναδρομή, εννοούσατε κυκλικές αναφορές πακέτων (είναι διαφορετικά πράγματα), έχω υποθέσει ότι η οργάνωση πακέτων ήταν εκτός πεδίου για αυτήν την ερώτηση, η παρουσιαζόμενη αφηρημένη κλάση με διεπαφή κάνει το κόλπο, οπότε δεν το κάνω δείτε την προτεινόμενη λύση σας πολύ διαφορετική από αυτή που έχω προτείνει, επιπλέον την έχετε αναλύσει ανά διαφορετικά πακέτα και προσθέσατε εργοστασιακή δομή 31.05.2018
  • @AdrianWitas Έχεις δίκιο, εννοούσα κυκλικές αναφορές, επεξεργασμένες. Ωστόσο, με τη λύση σας θα αντιμετωπίσετε στην πραγματικότητα κυκλικά ζητήματα, εάν χρησιμοποιείτε ένα εργοστασιακό μοτίβο, δοκιμάστε το 02.06.2018
  • @Juan Carlos Diaz, δεν βλέπω κανένα πρόβλημα κυκλικής αναφοράς πακέτου: δείτε την επέκταση εργοστασίου σε ένα πακέτο play.golang .org/p/Tu0RNmRWHsa , επομένως, όπως αναφέρθηκε, η μόνη διαφορά μεταξύ των παρουσιαζόμενων λύσεων είναι η ανάθεση σε πακέτα (έχει τη δική του αξία) και η προσθήκη εργοστασίου. Εκτιμήστε τη διόρθωση. 02.06.2018
  • @AdrianWitas φυσικά δεν θα δείτε κανένα κυκλικό πρόβλημα με αυτόν τον σύνδεσμο παιδικής χαράς, κάνετε τα πάντα σε ένα μόνο αρχείο, ένα πακέτο, αυτό δεν είναι καθόλου πραγματικό εργοστασιακό σενάριο. 03.06.2018
  • @AdrianWitas. Ήθελα να απαντήσω εξηγώντας, αλλά τα σχόλια περιορίζονται με μέγιστο αριθμό χαρακτήρων, γι' αυτό πρόσθεσα τα σχόλιά μου εδώ play .golang.org/p/Qi6sKbOF7Ob Και παρακαλώ, αν έχετε κάποια πρόταση, ενημερώστε με. Ο τρόπος που το έκανα ήταν με τις προτάσεις σας, αλλά με μια μικρή αλλαγή στην αναδίπλωση των αφηρημένων μελών στη δική του διεπαφή 03.06.2018
  • @Juan Carlos Diaz, υποθέτω ότι μπορείτε εύκολα να καταλήξετε με κυκλικές αναφορές πακέτων ανεξάρτητα από το πρόβλημα που αντιμετωπίζεται, αλλά αυτό δεν έχει καμία σχέση με την αφηρημένη λύση τάξης, δείτε ότι έχω χρησιμοποιήσει το μοτίβο που παρουσιάζεται εδώ github.com/viant/dsc/blob/master/manager.go και github.com/viant/bgc/blob/master/manager.go και σε πολλά άλλα μέρη χωρίς να αντιμετωπίζετε πρόβλημα κυκλικών αναφορών, ελπίζω να βοηθήσει να διευκρινιστεί. 03.06.2018

  • 5

    Η λειτουργικότητα της αφηρημένης κλάσης έχει τις παρακάτω απαιτήσεις 1. Δεν θα πρέπει να είναι δυνατή η δημιουργία άμεσης παρουσίας της κλάσης αφηρημένης 2. Θα πρέπει να παρέχει προεπιλεγμένα πεδία και μεθόδους.

    Ένας συνδυασμός διεπαφής και δομής μπορεί να χρησιμοποιηθεί για την εκπλήρωση των παραπάνω δύο απαιτήσεων. Για παράδειγμα μπορούμε να δούμε παρακάτω

    package main
    
    import "fmt"
    
    //Abstract Interface
    type iAlpha interface {
        work()
        common(iAlpha)
    }
    
    //Abstract Concrete Type
    type alpha struct {
        name string
    }
    
    func (a *alpha) common(i iAlpha) {
        fmt.Println("common called")
        i.work()
    }
    
    //Implementing Type
    type beta struct {
        alpha
    }
    
    func (b *beta) work() {
        fmt.Println("work called")
        fmt.Printf("Name is %s\n", b.name)
    }
    
    func main() {
        a := alpha{name: "test"}
        b := &beta{alpha: a}
        b.common(b)
    }
    
    
      Output:
        common called
        work called
        Name is test
    

    Ένα σημαντικό σημείο που πρέπει να αναφέρουμε εδώ είναι ότι όλες οι προεπιλεγμένες μέθοδοι θα πρέπει να έχουν το iAlpha ως πρώτο όρισμα και εάν η προεπιλεγμένη μέθοδος χρειάζεται να καλέσει οποιαδήποτε μέθοδο που δεν έχει εφαρμοστεί, θα καλέσουν σε αυτήν τη διεπαφή. Αυτό είναι το ίδιο όπως κάναμε στην κοινή μέθοδο παραπάνω - i.work().

    Πηγή: https://golangbyexample.com/go-abstract-class/

    18.08.2019
    Νέα υλικά

    Code Smell 210 - Dynamic Properties
    Η τεμπελιά και η μαγεία φέρνουν ελαττώματα TL;DR: Να είστε ξεκάθαροι με τα χαρακτηριστικά σας Προβλήματα Ευανάγνωστο Ορισμός πεδίου εφαρμογής Απαρατήρητα τυπογραφικά λάθη..

    Οπτικοποίηση δεδομένων με γραφήματα Google
    του Ejiro Thankgod Η οπτικοποίηση δεδομένων είναι μια εξαιρετική προσέγγιση για την εμφάνιση δεδομένων και τη συμμετοχή κοινού. Ο ανθρώπινος νους είναι συχνά καλύτερος στο να θυμάται..

    Το όνομά μου είναι Abdul Qudus.abd
    Το όνομά μου είναι Abdul Qudus.abd Είμαι διαδικτυακός συγγραφέας και γράφω άρθρα. Έχω 3ετή εμπειρία στη διαδικτυακή συγγραφή. οπότε θα είμαι ο καλύτερος συγγραφέας στην πλατφόρμα σας. είμαι ο..

    Το Python Selenium Web Scraping σας κατέβασε το 2023; Εκκαθαρίστε την προσωρινή μνήμη και ενισχύστε τα αποτελέσματά σας!
    Η απόξεση ιστού με το Python Selenium είναι ένα εξαιρετικό εργαλείο για την εξαγωγή δεδομένων από ιστοσελίδες. Ωστόσο, περιστασιακά, οι ιστοσελίδες ενδέχεται να αποτύχουν να φορτώσουν ή να..

    Έννοιες JavaScript που πρέπει να γνωρίζετε για να ξεκινήσετε με το React
    Αφού είδα αυτή την ερώτηση να τίθεται αμέτρητες φορές, πιστεύω ότι μπορεί να είναι χρήσιμο σε πολλούς ανθρώπους εκεί έξω να έχουν μια σαφή κατανόηση του τι χρειάζεται να είναι γνωστό στο..

    Χρήση της C++ ως γλώσσας δέσμης ενεργειών, μέρος 8
    Βελτίωση κλήσεων λειτουργιών API με χρήση ενσωματωμένης συναρμολόγησης Έχω πειραματιστεί με την ενσωματωμένη συναρμολόγηση στο παρελθόν με κάποια επιτυχία. Είναι περίπλοκο και εύκολο να κάνεις..

    5 αμυχές που πρέπει να γνωρίζετε για να γίνετε σπουδαίος προγραμματιστής.
    5 αμυχές που πρέπει να γνωρίζετε για να γίνετε σπουδαίος προγραμματιστής. Αυτές τις μέρες, πολλοί φιλοδοξούν να εργαστούν ως προγραμματιστές. Αλλά το να γίνεις σπουδαίος προγραμματιστής δεν..