学习日记:入门1

终于组织了自己,开始学习Go。正如预期的那样,我决定立即开始练习,以更好地使用该语言。我提出了一项“实验室工作”,其中我计划合并该语言的各个方面,同时不要忘记使用其他语言进行开发的现有经验,尤其是-各种体系结构原理,包括SOLID等。我在实现构想本身的过程中写了这篇文章,表达了我对如何完成这一部分或那部分工作的主要想法和考虑。因此,这不是一篇像一堂课的文章,我试图在此教别人如何做,而是教我自己的思想和对历史的推理记录,以便以后在进行错误工作时可以参考。

介绍性

该实验室的本质是使用控制台应用程序保存费用日记。该功能初步如下:

  • 用户可以指定当日,金额和注释,记录当日和过去任意一天的新费用记录

  • 它还可以按日期进行选择,从而获得在输出中花费的总金额

正规化

因此,根据业务逻辑,我们有两个实体:一个单独的费用记录(Expense)和一个通用实体Diary,该实体将整个支出日记拟人化。费用日期金额注释类的字段组成日记尚未包含任何内容,只是简单地以一种或另一种方式将日记本身整体化,包含一组Expense对象,并因此可以出于各种目的而对其进行获取/修改。其更多领域和方法将在下面看到。由于我们正在谈论的是记录的顺序列表,尤其是按日期排序,因此以实体链接列表的形式实现的方法本身就是一种建议。在这种情况下,对象日记只能引用列表中的第一项。它也需要补充的基本方法操作元件(添加/删除等),但你不应该填补这个对象,使其太过火没有考虑太多,也就是说,它并不矛盾单一职责原则(单责任-SOLID中的字母S)。例如,您不应添加将日记保存到文件或从中读取文件的方法。以及任何其他特定的分析和数据收集方法。对于文件,这是体系结构(存储)独立层,与业务逻辑不直接相关。在第二种情况下,使用日记的选项是事先未知的,并且可能会有很大差异。,这将不可避免地导致恒定的变化日记,这是非常不可取的。因此,所有其他逻辑将不在此类之内。

贴近身体,即实现

总体而言,如果我们投入更多精力并谈论Go中的特定实现,我们将具有以下结构:

//     
type Expense struct {
  Date time.Date
  Sum float32
  Comment string
}

//  
type Diary struct {
  Entries *list.List
}

最好使用诸如容器/列表包之类通用解决方案来处理链表这些结构定义应该放在一个单独的包中,我们将其称为支出:让我们在项目内部创建一个包含两个文件的目录:Expense.go和Diary.go。

/ , / . , : ( ), - -, , , . . , , . : Save(d *Diary) Load() (*Diary). : DiarySaveLoad, expenses/io:

type DiarySaveLoad interface {
	Save(diary *expenses.Diary)
	Load() *expenses.Diary
}

, /, / (, , - - URL , ). , . , (Liskov substitution - L SOLID), . -, / , : Save Load . , , , , , , DiarySaveLoadParameters, /, . . (Interface segregation - I SOLID), , .

, : FileSystemDiarySaveLoad. , “ ”, - / :

package io

import (
	"expenses/expenses"
	"fmt"
	"os"
)

type FileSystemDiarySaveLoad struct {
	Path string
}

func (f FileSystemDiarySaveLoad) Save(d *expenses.Diary) {
	file, err := os.Create(f.Path)
	if err != nil {
		panic(err)
	}

	for e := d.Entries.Front(); e != nil; e = e.Next() {
		buf := fmt.Sprintln(e.Value.(expenses.Expense).Date.Format(time.RFC822))
		buf += fmt.Sprintln(e.Value.(expenses.Expense).Sum)
		buf += fmt.Sprintln(e.Value.(expenses.Expense).Comment)
		if e.Next() != nil {
			buf += "\n"
		}

		_, err := file.WriteString(buf)
		if err != nil {
			panic(err)
		}
	}
	err = file.Close()
}

:

func (f FileSystemDiarySaveLoad) Load() *expenses.Diary {
	file, err := os.Open(f.Path)
	if err != nil {
		panic(err)
	}

	scanner := bufio.NewScanner(file)
	entries := new(list.List)
	var entry *expenses.Expense
	for scanner.Scan() {
		entry = new(expenses.Expense)
		entry.Date, err = time.Parse(time.RFC822, scanner.Text())
		if err != nil {
			panic(err)
		}
		scanner.Scan()
		buf, err2 := strconv.ParseFloat(scanner.Text(), 32)
		if err2 != nil {
			panic(err2)
		}
		entry.Sum = float32(buf)
		scanner.Scan()
		entry.Comment = scanner.Text()
		entries.PushBack(*entry)
		entry = nil
		scanner.Scan() // empty line
	}

	d := new(expenses.Diary)
	d.Entries = entries

	return d
}

“ ”, / . , , expenses/io/FileSystemDiarySaveLoad_test.go:

package io

import (
	"container/list"
	"expenses/expenses"
	"math/rand"
	"testing"
	"time"
)

func TestConsistentSaveLoad(t *testing.T) {
  path := "./test.diary"
  d := getSampleDiary()
	saver := new(FileSystemDiarySaveLoad)
	saver.Path = path
	saver.Save(d)

	loader := new(FileSystemDiarySaveLoad)
	loader.Path = path
	d2 := loader.Load()

	var e, e2 *list.Element
	var i int

	for e, e2, i = d.Entries.Front(), d2.Entries.Front(), 0; e != nil && e2 != nil; e, e2, i = e.Next(), e2.Next(), i+1 {
		_e := e.Value.(expenses.Expense)
		_e2 := e2.Value.(expenses.Expense)

		if _e.Date != _e2.Date {
			t.Errorf("Data mismatch for entry %d for the 'Date' field: expected %s, got %s", i, _e.Date.String(), _e2.Date.String())
		}
    //      Expense ...
	}

	if e == nil && e2 != nil {
		t.Error("Loaded diary is longer than initial")
	} else if e != nil && e2 == nil {
		t.Error("Loaded diary is shorter than initial")
	}
}

func getSampleDiary() *expenses.Diary {
	testList := new(list.List)

	var expense expenses.Expense

	expense = expenses.Expense{
		Date:    time.Now(),
		Sum:     rand.Float32() * 100,
		Comment: "First expense",
	}
	testList.PushBack(expense)

  //    
  // ...

	d := new(expenses.Diary)
	d.Entries = testList

	return d
}

, , . , /: , , . go test expenses/expenses/io -v

FAIL :

Data mismatch for entry 0 for the 'Date' field: expected 2020-09-14 04:16:20.1929829 +0300 MSK m=+0.003904501, got 2020-09-14 04:16:00 +0300 MSK

: . , time.Now, . : / RFC822, , , . . , , , , ( ), . . , . SOLID, , (Open-closed principle - O SOLID). , . , -, . , , , - , , Expense. , Go , expenses:

func Create(date time.Time, sum float32, comment string) Expense {
	return Expense{Date: date.Truncate(time.Second), Sum: sum, Comment: comment}
}

, Expense ( :D), : Load FileSystemDiarySaveLoad, ( getSampleDiary). . , , , , time.RFC3339Nano . , , , .

. :) , / , , . :) , Diary, . . ( container/list) - "" Diary, - . () Diary, , , . .

, Go, , - Go. , , : , . , . , :)

PS该项目的存储库位于https://github.com/Amegatron/golab-expensesmaster分支将包含该作品的最新版本。标签tag)将标记根据每个文章进行的最后一次提交。例如,根据本文的最后一次提交(条目1)将被标记为stage_01




All Articles