Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- defmodule Product do
- defstruct name: '', count: 1
- defimpl String.Chars, for: Product do
- def to_string(product) do
- "Product Name: #{product.name} -- Product Count: #{product.count}"
- end
- end
- end
- defmodule FridgeState do
- defstruct productList: [], fridgeState: 'closed'
- defimpl String.Chars, for: FridgeState do
- def to_string(state) do
- "Fridge Products: #{state.productList} -- Fridge State: #{state.fridgeState}"
- end
- end
- end
- defmodule Fridge do
- use GenServer
- ########################
- # START/STOP FUNCTIONS #
- ########################
- def start_link() do
- GenServer.start_link(__MODULE__, %FridgeState{})
- end
- def stop(pid) do
- GenServer.stop(pid, :normal, :infinity)
- end
- #######
- # API #
- #######
- def open(pid), do: GenServer.call(pid, :open)
- def close(pid), do: GenServer.call(pid, :close)
- def put(pid, product), do: GenServer.call(pid, {:put, product})
- def pull(pid, product), do: GenServer.call(pid, {:pull, product})
- ####################
- # SERVER CALLBACKS #
- ####################
- @doc """
- This method is called when the Fridge instance of the GenServer is initialized
- Този метод се използва изцяло за инициализация. Тук можем да си слагаме някакви API извиквания към външни service-и.
- """
- @impl true
- def init(fridge) do
- {:ok, fridge}
- end
- @doc """
- This method is called when the Fridge instance of the GenServer is terminated
- Този метод се извиква при спиране на GenServer-а. Тук можем да си затваряме всякакви връзки с други приложения или да зачистваме използваната от нас памет.
- """
- @impl true
- def terminate(_reason, _list) do
- :ok
- end
- ################
- # API HANDLERS #
- ################
- @doc """
- This method is used to change the fridge state from closed to open.
- Този метод е наследен от макрото use GenServer. Чрез него могат да се правят извиквания на
- команди по наш избор. Първият параметър е ключовата дума на командата. Вторият параметър е
- променлива, която съдържа в себе си адреса извикващият командата. Третият параметър е наша
- собствена променлива, която дефинираме като state или състояние на GenServer-а ни.
- """
- @impl true
- def handle_call(:open, _from, state) do
- #
- # Понеже в Elixir няма как да се промени вече съществуваща променлива, се създава нова такава.
- # С променените параметри, които искаме да се използват.
- #
- # Подаваме текущият списък с продукти, който имаме за целият lifecycle на приложението (state.productList).
- # След което си променяме fridgeState променливата на closed.
- #
- updatedState = %FridgeState{productList: state.productList, fridgeState: 'open'}
- # Връща се отговор на командата, която е заявена с обновеното състояние на приложението.
- {:reply, updatedState, updatedState}
- end
- @doc """
- This method is used to change the fridge state from open to closed.
- Този метод е наследен от макрото use GenServer. Чрез него могат да се правят извиквания на
- команди по наш избор. Първият параметър е ключовата дума на командата. Вторият параметър е
- променлива, която съдържа в себе си адреса извикващият командата. Третият параметър е наша
- собствена променлива, която дефинираме като state или състояние на GenServer-а ни.
- """
- @impl true
- def handle_call(:close, _from, state) do
- #
- # Понеже в Elixir няма как да се промени вече съществуваща променлива, се създава нова такава.
- # С променените параметри, които искаме да се използват.
- #
- # Подаваме текущият списък с продукти, който имаме за целият lifecycle на приложението (state.productList).
- # След което си променяме fridgeState променливата на closed.
- #
- updatedState = %FridgeState{productList: state.productList, fridgeState: 'closed'}
- # Връща се отговор на командата, която е заявена с обновеното състояние на приложението.
- {:reply, updatedState, updatedState}
- end
- @doc """
- This method handles the 'put' client call.
- Този метод се използва за обработката на put командата към Fridge структурата.
- Първият параметър се нарича tuple. Тоест имаме две променливи в една, които можем да ги достъпим.
- Първата променлива служи за име на командата, а втората променлива е параметъра, който е нужен за подаване.
- Вторият параметър е променлива, която съдържа в себе си адреса извикващият командата.
- Третият параметър е наша собствена променлива, която дефинираме като state или състояние на GenServer-а ни.
- """
- def handle_call({:put, product}, _from, state) do
- #
- # Ако хладилника е отворен или ако е различно от отворен
- #
- if state.fridgeState != 'closed' do
- #
- # Създаваме си една анонимна функция или ламбда функция,
- # за да не пишем като луди всеки път условието на фунцкиите,
- # които ще използваме за намиране на дадени елементи от списъка
- # ни с продукти.
- #
- predicate = fn pd -> pd.name === product.name end
- #
- # Създаваме си tuple с три променливи.
- #
- # Първата променлива ще я използваме, за да проверим дали такъв продукт
- # съществува в списъка ни с продукти.
- #
- # Втората променлива ще я използваме, за да намерим индекса на елемента,
- # който търсим, за да можем да променим стойността на елемента в списъка.
- #
- # И третата променлива ще я използваме, за да намерим текущият продукт, който
- # искаме да добавим.
- #
- {
- isProductFound,
- productIndex,
- currentProduct
- } = {
- Enum.any?(state.productList, predicate),
- Enum.find_index(state.productList, predicate),
- Enum.find(state.productList, false, predicate)
- }
- #
- # Ако продуктът вече съществува
- #
- if isProductFound do
- #
- # Създаваме си нов продукт, който е модифицирана версия на текущият ни.
- # Прави се с цел да няма повтарящи се продукти, а като се срещне два пъти, например, мляко,
- # да увеличи продуктите с единица, а не да добави нов продукт със същото име и count 1.
- #
- updatedProduct = %Product{name: currentProduct.name, count: currentProduct.count + 1}
- #
- # Създаваме си нов списък, който е модифицирана версия на нашият списък с продукти,
- # като в този списък ще заменим продукта с индекс productIndex с нашият модифициран
- # продукт.
- #
- updatedList = List.replace_at(state.productList, productIndex, updatedProduct)
- #
- # Създаваме си нова версия на състоянието на приложението с обновената информация.
- #
- updatedState = %FridgeState{ productList: updatedList, fridgeState: state.fridgeState }
- #
- # Връщаме отговор с модифицираното състояние на приложението
- #
- {:reply, updatedState, updatedState}
- else
- #
- # Създаваме си нов списък, който е модифицирана версия на нашият списък с продукти,
- # като в този списък ще добавим продукта, който сме подали като параметър.
- #
- updatedList = [product|state.productList]
- #
- # Създаваме си нова версия на състоянието на приложението с обновената информация.
- #
- updatedState = %FridgeState{ productList: updatedList, fridgeState: state.fridgeState }
- #
- # Връщаме отговор с модифицираното състояние на приложението
- #
- {:reply, updatedState, updatedState}
- end
- else
- #
- # В случай, че състоянието на Fridge-а е 'closed', връщаме текущото състояние на
- # приложението с информативно съобщение 'Unable to use the fridge while closed'.
- #
- {:reply, "Unable to use fridge while closed", state}
- end
- end
- @doc """
- This method handles the 'pull' client call.
- Този метод се използва за обработката на pull командата към Fridge структурата.
- Първият параметър се нарича tuple. Тоест имаме две променливи в една, които можем да ги достъпим.
- Първата променлива служи за име на командата, а втората променлива е параметъра, който е нужен за подаване.
- Вторият параметър е променлива, която съдържа в себе си адреса извикващият командата.
- Третият параметър е наша собствена променлива, която дефинираме като state или състояние на GenServer-а ни.
- """
- def handle_call({:pull, product}, _from, state) do
- #
- # Ако хладилника е отворен или ако е различно от отворен
- #
- if state.fridgeState != 'closed' do
- #
- # Създаваме си една анонимна функция или ламбда функция,
- # за да не пишем като луди всеки път условието на фунцкиите,
- # които ще използваме за намиране на дадени елементи от списъка
- # ни с продукти.
- #
- predicate = fn pd -> pd.name === product.name end
- #
- # Създаваме си tuple с три променливи.
- #
- # Първата променлива ще я използваме, за да проверим дали такъв продукт
- # съществува в списъка ни с продукти.
- #
- # Втората променлива ще я използваме, за да намерим индекса на елемента,
- # който търсим, за да можем да променим стойността на елемента в списъка.
- #
- # И третата променлива ще я използваме, за да намерим текущият продукт, който
- # искаме да добавим.
- #
- {
- isProductFound,
- productIndex,
- currentProduct
- } = {
- Enum.any?(state.productList, predicate),
- Enum.find_index(state.productList, predicate),
- Enum.find(state.productList, false, predicate)
- }
- #
- # Ако продуктът вече съществува
- #
- if isProductFound do
- #
- # Проверяваме резултата, ако извадим -1 дали ще остане под 1
- # Ако остане, го изтриваме от списъка с продукти.
- #
- if (currentProduct.count - 1) < 1 do
- #
- # Създаваме си нов списък, който е модифицирана версия на нашият списък с продукти,
- # като в този списък ще изтрием текущият продукт.
- #
- updatedList = List.delete(state.productList, currentProduct)
- #
- # Създаваме си нова версия на състоянието на приложението с обновената информация.
- #
- updatedState = %FridgeState{ productList: updatedList, fridgeState: state.fridgeState }
- #
- # Връщаме отговор с модифицираното състояние на приложението
- #
- {:reply, updatedState, updatedState}
- else
- #
- # Създаваме си нов продукт, който е модифицирана версия на текущият ни.
- #
- #
- updatedProduct = %Product{name: currentProduct.name, count: currentProduct.count - 1}
- #
- # Създаваме си нов списък, който е модифицирана версия на нашият списък с продукти,
- # като в този списък ще заменим продукта с индекс productIndex с нашият модифициран
- # продукт.
- #
- updatedList = List.replace_at(state.productList, productIndex, updatedProduct)
- #
- # Създаваме си нова версия на състоянието на приложението с обновената информация.
- #
- updatedState = %FridgeState{ productList: updatedList, fridgeState: state.fridgeState }
- #
- # Връщаме отговор с модифицираното състояние на приложението
- #
- {:reply, updatedState, updatedState}
- end
- else
- #
- # В случай, че не намери продукта, който търсим, връщаме отговор с текущото
- # състояние на приложението и съобщение 'Product not found'.
- #
- {:reply, "Product not found", state}
- end
- else
- #
- # В случай, че състоянието на Fridge-а е 'closed', връщаме текущото състояние на
- # приложението с информативно съобщение 'Unable to use the fridge while closed'.
- #
- {:reply, "Unable to use fridge while closed", state}
- end
- end
- end
- @doc """
- Създаваме си входна точка на приложението
- """
- defmodule Main do
- @doc """
- Създаваме си входна точка на приложението
- """
- def main do
- #
- # Стартираме нашият GenServer, който ни връща tuple от състояние
- # дали е запалил успешно и pid - id на процеса.
- #
- # pid-а се ползва за всяка една функция на GenServer-а.
- #
- {:ok, pid} = Fridge.start_link()
- #
- # Започваме да използваме нашите декларирани клиентски методи.
- #
- Fridge.open(pid)
- Fridge.put(pid, %Product{name: 'eggs'})
- Fridge.close(pid)
- Fridge.open(pid)
- Fridge.put(pid, %Product{name: 'cheese'})
- Fridge.close(pid)
- Fridge.open(pid)
- Fridge.put(pid, %Product{name: 'cheese'})
- Fridge.close(pid)
- Fridge.open(pid)
- Fridge.put(pid, %Product{name: 'cheese'})
- Fridge.close(pid)
- Fridge.open(pid)
- Fridge.put(pid, %Product{name: 'cheese'})
- Fridge.close(pid)
- #
- # Започваме да изваждаме продукти
- #
- Fridge.open(pid)
- Fridge.pull(pid, %Product{name: 'cheese'})
- Fridge.close(pid)
- Fridge.open(pid)
- Fridge.pull(pid, %Product{name: 'cheese'})
- Fridge.close(pid)
- failedState = Fridge.pull(pid, %Product{name: 'eggs'})
- #
- # Проверяваме какво ни е върнало като резултат, ако
- # не сме отворили хладилника.
- #
- IO.inspect failedState
- #
- # Спираме нашият GenServer - Fridge.
- #
- Fridge.stop(pid)
- end
- end
- #
- # Извикваме входната точка на приложението.
- #
- Main.main
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement