Сокрытие информации в Go
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 50 из 107.
Скрытие информации выходит за рамки простого объявления полей неэкспортируемыми. Речь идет о проектировании ваших типов таким образом, чтобы внешний код знал только то, что ему необходимо знать, и ничего больше.
Цель состоит в том, чтобы предоставить доступ к поведению через методы, сохраняя при этом внутреннюю структуру полностью скрытой. Рассмотрим этот подход:
type Counter struct {
value int
maxLimit int
}
func NewCounter(limit int) *Counter {
return &Counter{maxLimit: limit}
}
func (c *Counter) Increment() bool {
if c.value >= c.maxLimit {
return false
}
c.value++
return true
}
func (c *Counter) Value() int {
return c.value
}
Внешний код не знает, что Counter использует int внутри. Это может быть float64, срез или что-то совсем другое. Реализация может измениться, не затрагивая код, использующий Counter, — при условии, что методы ведут себя так же.
Этот принцип также применим к вспомогательным функциям. Оставляйте внутреннюю логику неэкспортируемой:
// Экспортируемая - часть публичного API
func (c *Counter) Reset() {
c.value = 0
}
// неэкспортируемая - внутренний помощник
func (c *Counter) isAtLimit() bool {
return c.value >= c.maxLimit
}
Скрывая isAtLimit(), вы можете свободно изменять или удалять его позже. Сокрытие информации создает четкую границу между тем, что ваш пакет обещает внешним пользователям, и тем, как он работает внутри.
Задание
ЛегкоДавайте создадим систему безопасного кошелька, которая демонстрирует сокрытие информации. Вы создадите цифровой кошелек, в котором внутреннее отслеживание баланса и история транзакций полностью скрыты от внешнего кода — наружу через методы выставляется только поведение.
Вы организуете свой код в двух файлах:
wallet.go: Создайте структуруWallet, которая скрывает все свое внутреннее состояние. Кошелек должен внутренне отслеживать текущий баланс и лимит расходов, но внешний код никогда не должен знать, как они хранятся или представлены. Предоставьте поведение через следующие методы:NewWallet(limit float64) *Wallet- конструктор, который создает кошелек с заданным лимитом расходов и нулевым балансомDeposit(amount float64) bool- добавляет деньги в кошелек, возвращаетtrueв случае успеха (сумма должна быть положительной)Spend(amount float64) bool- списывает деньги, если баланса достаточно И сумма не превышает лимит расходов, возвращаетtrueв случае успехаBalance() float64- возвращает текущий балансStatus() string- возвращает сообщение о статусе в формате:Balance: $[balance] (Limit: $[limit])
main.go: Считайте конфигурацию кошелька и транзакции из входных данных, создайте кошелек, выполните операции и выведите результаты после каждого действия.
Будут предоставлены следующие входные данные:
- Строка 1: Лимит расходов
- Строка 2: Сумма депозита
- Строка 3: Сумма первой траты
- Строка 4: Сумма второй траты
После создания кошелька выведите его начальный статус. Затем выполните каждую операцию и выведите либо обновленный статус (в случае успеха), либо Transaction failed (если операция была отклонена). Форматируйте все денежные суммы с двумя знаками после запятой.
Например, для входных данных 50, 100, 30 и 80 ваш вывод должен быть следующим:
Balance: $0.00 (Limit: $50.00)
Balance: $100.00 (Limit: $50.00)
Balance: $70.00 (Limit: $50.00)
Transaction failedПоследняя транзакция не удалась, потому что $80 превышает лимит расходов в $50 — даже несмотря на то, что баланса достаточно. Кошелек применяет свои правила внутренне, не раскрывая, как он принимает эти решения. Внешний код просто вызывает методы и получает результаты, никогда не зная деталей внутренней реализации.
Попробуйте сами
package main
import (
"fmt"
)
func main() {
// Чтение входных данных
var limit float64
var depositAmount float64
var spend1 float64
var spend2 float64
fmt.Scanln(&limit)
fmt.Scanln(&depositAmount)
fmt.Scanln(&spend1)
fmt.Scanln(&spend2)
// TODO: Создать новый кошелек с заданным лимитом расходов
// TODO: Вывести начальный статус
// TODO: Выполнить пополнение и вывести результат (статус или "Transaction failed")
// TODO: Выполнить первый расход и вывести результат
// TODO: Выполнить второй расход и вывести результат
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool