Given a site mannequin for a simplified todo app
sort Todo struct {
title string
isMarkedAsDone bool
modifiedAt time.Time
}
func NewTodo(title string) (*Todo, error) {
if title == "" {
return nil, errors.New("title is empty")
}
todo := &Todo{
title: title,
isMarkedAsDone: false,
modifiedAt: time.Now(),
}
return todo, nil
}
func (t *Todo) GetTitle() string {
return t.title
}
func (t *Todo) IsMarkedAsDone() bool {
return t.isMarkedAsDone
}
// different getters...
func (t *Todo) Rename(newTitle string) error {
if t.isMarkedAsDone {
return errors.New("todo is already marked as accomplished")
}
if newTitle == "" {
return errors.New("new title is empty")
}
t.title = newTitle
t.modifiedAt = time.Now()
return nil
}
func (t *Todo) MarkAsDone() error {
if t.isMarkedAsDone {
return errors.New("todo is already marked as accomplished")
}
t.isMarkedAsDone = true
t.modifiedAt = time.Now()
return nil
}
// different setters...
Saving this todo to a retailer isn’t any drawback since I can entry the fields by way of getters. However once I question the shop for todos which are marked as accomplished I can’t reconstruct a site object from the returned information.
The constructor operate takes no isMarkedAsDone = true
parameter ( + isMarkedAsDone
) and if I’d attempt to create a brand new area todo and name the MarkAsDone
operate on it I’d overwrite the sector modifiedAt
which is incorrect then.
What’s a standard strategy to unravel this?
- Make the whole lot public? ( feels incorrect to me, customers may put the area object in invalid state )
- Change the entire constructor operate to simply accept all fields and validate all of them, so customers have to supply all fields from outdoors?
- Preserve the whole lot as is however present a
reconstruct
operate in the identical package deal accepting all fields ( + validation ) that creates a site mannequin and has write entry to the personal fields because it lives in the identical package deal?
The idiom for this kinf of drawback in Go is use optionally available Sample. Please test
And in some instances i aldo apply Builder sample
Thanks in your reply. However how would you deal with the modifiedAt
subject? Having a WithModifiedAt
-function?
Usually you’d by no means have such a operate as a result of this subject shall be set on each modification. So if you happen to go for the choices sample you might additionally setup one large operate that validates each parameter, no?
Sure, it doesn’t want a getter/setter and create an possibility operate per subject
True, however when calling a operate like Rename
or MarkAsDone
on Todo
you’d nonetheless replace the modifiedAt
subject, proper?
What if customers then name WithModifiedAt
once more on it? I personally assume they shouldn’t have the ability to take action …
That is the most suitable choice imo.
Making all fields public violates encapsulation and accepting all fields in constructor can result in misusing it.
Hello Joachim,
I observed this query is a bit older, however I wished to supply some suggestions on the code. Please take this as a pleasant suggestion to make your code extra approachable.
It looks like many individuals code on this model, and I’m curious why that’s. Maybe it’s seen as a intelligent strategy to code, however generally simplicity could be more practical and simpler to grasp for everybody.
I wish to present some recommendation that may assist others who come throughout this thread as properly.
As an alternative of specializing in a standard strategy, why not purpose for a very good strategy? A website mannequin may not be obligatory, and also you don’t want constructors on this case. You may simply initialize the app struct.
Right here’s my suggestion:
As an alternative of utilizing features straight in your Todo, think about using the features in your database connector. Use your db.Fashions straight as an alternative of redefining them. (Assuming you’re utilizing SQL to retailer todos persistently.)?
Right here’s some pseudocode as an example:
sort App struct {
Queries *db.Queries
}
func (app *App) getTodo(todoId uuid.UUID) (todo db.Todo, err error) {
todo, err := app.Queries.GetTodo(context.TODO(), db.GetTodoParams{ID: todoId})
// error dealing with
return todo, err
}
func (app *App) createTodo(title string) (err error) {
_, err := app.Queries.CreateTodo(context.TODO(), db.CreateTodoParams{Title: title})
// error dealing with
return err
}
Attempt to keep away from pressured OOP. Because you’re engaged on todo lists, it’s an awesome alternative to observe. By implementing each approaches, you’ll have the ability to see which one works higher. Usually, the easier answer proves to be more practical, and real-life duties will introduce sufficient complexity on their very own.
Go is straightforward.
I hope this helps. Have a beautiful weekend!