首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Go中采用中介模式的师生课堂建模

Go中采用中介模式的师生课堂建模
EN

Code Review用户
提问于 2018-09-22 10:07:12
回答 1查看 128关注 0票数 0

我用中介模式实现了教师和学生的课堂教学。调解员是教师,同事是学生。

主要功能如下。主要功能是创建一名教师,然后她说将教给一些学生。最后,她试图传播一个概念,一个学生会告诉他(或她)学到了什么。

代码语言:javascript
复制
func NewClassMate(name string) Student {
    return &ClassMate{name, "meeting 1", ""}
}

func main() {

    teacher := Teacher{}

    student := NewClassMate("Mario")

    teacher.TeachesTo(student)
    teacher.TeachesTo(NewClassMate("Demo"))
    teacher.TeachesTo(NewClassMate("Mattia"))
    teacher.TeachesTo(NewClassMate("Simone"))

    fmt.Println(len(teacher.attendees))
    fmt.Println(student.Forum())

    teacher.Spread("Message sent to everyone")

    fmt.Println(student.Learned())

}

教师

教师的实施是微不足道的。老师认识所有的学生并传播课程。

代码语言:javascript
复制
type Teacher struct {
    students []Student
}

func (m *Teacher) TeachesTo(c Student) {
    m.students = append(m.students, c)
}

func (m *Teacher) Spread(message string) {
    for _, a := range m.students {
        a.Learn(message)
    }
}

学生

学生也很简单。它参加一个课程,学习并说出他所学的东西。

代码语言:javascript
复制
type ClassMate struct {
    lastMessage string
    forum       string
}

func (a *ClassMate) Class() string {
    return a.forum
}

func (a *ClassMate) Learn(message string) {
    a.lastMessage = message
}

func (a *ClassMate) Learned() string {
    return a.lastMessage
}

调解员及其同事

最后,我们可以看到根据模式选择的接口。

代码语言:javascript
复制
type Student interface {
    Class() string
    Learn(message string)
    Learned() string
}

type Mediator interface {
    TeachesTo(c *Student)
    Spread(messqger string)
}
EN

回答 1

Code Review用户

回答已采纳

发布于 2018-09-23 15:35:19

好的,让我们从上面开始:这是一个中介模式吗?

我认为您所拥有的是中介模式的两个组件(两个相互作用的对象),而没有实际的中介。只需引用中介模式的wiki条目的第一行:

在软件工程中,中介模式定义了一个封装了一组对象如何交互的对象。

所以它是一个对象,它封装了一组对象之间的交互。你所做的是创建两种类型的互动,因为它是模仿一个教师/学生的例子,其中学生是相当被动的,你说老师是调解人。然而:教师如何封装一组对象(即学生)的交互作用?事实并非如此。

不过,这并不是坏事,因为您可能来自另一种更经典的OOP语言,并且您正在尝试复制您习惯的go语言。这是人们经常犯的错误。然而,Go并不是一种经典的OO语言。它没有类,但是有(ducktype/隐式)接口。它没有继承,但有组成。它没有异常,没有特定的东西,比如静态属性或静态方法。所以,请不要强迫自己坚持经典的OO模式。利用自由和表现力带来的稍微不同的类型系统。

其他与您的代码直接相关的批评:

代码语言:javascript
复制
func (a *ClassMate) Class() string {
   return a.forum
}

为什么在这里使用指针接收器?这基本上使本应完全安全的功能变得不安全。想象一下:

代码语言:javascript
复制
go func() {
   student.forum = "some random message"
}()
fmt.Printf("student says: %s", student.Forum()) // or whatever

输出结果是什么?你能确定一切都是一样的吗?

这同样适用于学生的Learned()功能和教师/“调解员”的Spread(messqger string)功能。尤其是老师,事实上。Spread函数可能同时启动,因为返回可能需要一些时间。如果你想象一个场景,有人最后会做这样的事情:

代码语言:javascript
复制
go teacher.Spread("some really important life-lesson")
teacher.TeachTo(anotherStudent)
// and some more calls like this
teacher.TeachTo(anotherStudent2)
teacher.TeachTo(anotherStudent3)

您对上述代码的结果是否可靠和可复制有多大的信心?我更希望在调用时能得到切片头的副本,而不是依赖于range循环的行为。当您在Spread函数中添加一些检查或其他逻辑时,您将面临行为更改的风险。

最后,正如我在评论中所指出的:TeachesTo(c *Student)是无效代码。Student是一个接口类型,没有指针到接口类型.它要么是指向实现上述接口的类型的指针,要么是接口本身的指针。

从代码本身开始,更多地介绍如何以一种更惯用的方式处理事物的一般性评论/建议。

我不禁注意到你接连把这件事说得太多了:

代码语言:javascript
复制
teacher.TeachesTo(student)

戈朗支持各种论点,只要一次把所有的学生都传进去。漏斗的名字也很误导人。如果我看到TeachesTo,我希望调用能够在传入对象时与传入的对象实际交互。我不希望稍后调用Spread来实际执行该操作。我希望能够打电话给TeachesTo,并完成它。我将按照以下方式将Mediator接口更改为ClassroomCourse接口:

代码语言:javascript
复制
type Course struct {
    EnterTeacher(t Teacher) error // Teacher being an interface
    EnterStudents(s ...Student) error // variadic, only requires a single call
    AddLesson(data []string) error // using []string, but probably should be structured
    StartLesson() error
    TeacherLeave() error
    StudentsLeave() error
    EndLesson() error
    GetComplaints() []string // complain type?
    GetBannedStudents() []Student
    GetScores() map[string]float64 // results by student name?
    // and so on...
}

现在我们有了一个调解员。我们只要增加一个老师,增加一个或多个课程,我们就可以开始/停止上课,得到某种状态(比如分数,禁止学生,抱怨老师与学生互动的方式等等)。

实现可以是一件简单的事情,比如:

代码语言:javascript
复制
type course struct {
    name       string
    t          Teacher
    s          []Student
    lessons    []Lesson // create a type that's fitting
    day        int // can be used as offset in lessons
    scores     map[string]float64
    complaints []string
    banned     map[string]Student
}

此方法的其他影响:您将注意到类型和字段都没有导出。它应该是这样的:中介的全部意义在于封装对象交互的方式。因为这个原因,你不应该让他们暴露在外部的互动中。

用法,相当简单

代码语言:javascript
复制
package school // or whatever

// google functional options if this is confusing you
type CourseOption func (c *course)

// add funcs like these for banned students, lessons, students, etc...
func SetCourseTeacher(t Teacher) CourseOption {
    return func(c *Course) {
        c.t = t
    }
}

func NewCourse(n string, opts ...CourseOption) Course {
    c := &course{
        name: n,
        scores: map[string]float64{},
        banned: map[string]Student{}, // initialise these to avoid issues when accessing these
    }
    for _, o := range opts {
        o(c)
    }
    return c
}
// a couple of implementation examples:

func (c *course) EnterStudents(s ...Student) error {
    // ensure we're not entering a banned student
    for i := range s {
        if _, ok := c.banned[s[i].Name()]; ok {
            return fmt.Errorf("student %s is banned", s[i].Name())
        }
        if _, ok := c.scores[s[i].Name()]; ok {
            return fmt.Errorf("student %s already entered", s[i].Name())
        }
    }
    // this is messy, but required
    for i := range s {
        s.scores[s[i].Name()] = 0 // add new name to score list
    }
    c.s = append(c.s, s...) // append students from arg to students in list
    return nil
}

正如您所看到的,我现在可以安全地添加任意数量的学生,并返回错误,以防其中一些已经被禁止,如果学生已经被添加,则返回一个不同的错误。它还正确地初始化了状态。现在,让我们看到一些实际的中介实现了:

代码语言:javascript
复制
func (c *course) StartLesson() error {
    if len(c.lessons) <= c.day {
        return fmt.Errorf("no lesson can be started, Last lesson %d of %d", c.day, len(c.lessons))
    }

    // mediator decides which lesson object the teacher uses, and determines the teacher should prepare it
    if err := c.t.PrepareLesson(c.lesson[c.day]); err != nil {
        return err
    }
    // pass in students to which to teach the prepared lessen to, like a mediator does
    if err := c.t.Teach(c.s...); err != nil {
        return err
    }
    return nil
}

func (c *course) EndLesson() error {
    if !c.t.InClass() {
        // obviously...
        return fmt.Errorf("teacher not in class, cannot end lesson")
    }
    // see if teacher banned anyone
    for _, banned := range c.t.ProblemStudents() {
        // find student in slice, cut from slice and add to banned map
        // at this point, a map of students is easier to handle, but I can't be bothered
        // to rewrite most of this answer ;-p
    }
    for i := range c.s {
        // listen to complaints from students, too
        c.complaints = append(c.complaints, c.s[i].Complaints()...)
    }
    // mediator checks if the lesson was important, and instructs the teacher
    // to prepare a test. How the test is taken, is part of the lesson preparation and the teaching implementations
    if c.lessons[c.day].Important() {
        c.t.PrepareTest()
    }
    c.day++ // increment this, so we don't accidentally start the same lesson again
    return nil
}

在这一点上,这变得相当复杂和复杂。它正遭受着经典的OEP(过度设计的编程)的折磨。高丽设计师想用他们的语言劝阻的东西。

请注意,我前面提供的所有代码在您将其与并发性一起使用时都是非常不安全的:根据定义,地图的使用是不安全的。如果有人打电话来:

代码语言:javascript
复制
go course.EnterStudents(studentSlice[4:]...)
go course.EnterStudents(studentSlice...)
studentSlice := append([]Student{prependStudent}, studentSlice[4:6]...)
go course.EnterStudents(studentSlice...)

猜猜会发生什么,就是:如果可能的话.(提示:不是)。

我建议您使用sync包,并且总是使用竞争检测器进行测试/编译。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/204179

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档