Advertisement
Guest User

Untitled

a guest
Aug 19th, 2019
150
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 8.41 KB | None | 0 0
  1. =begin
  2.  
  3. This is a simple example of some Object Oriented Programming focusing on a dynamic Animal class that includes some basic
  4. functionality to model animals being able to eat certain foods and gain stats from said food. This is only an abstraction
  5. to a base level of interaction and isn't wholly representative of the actual reality of feeding animals a diverse diet, but
  6. the overarching concepts of OO pop out in a few ways:
  7.  
  8.   1. Each class has methods that maintain single responsibility. Essentially, they execute a single action or a related set of actions that make sense
  9.      for the level of logical abstraction you are going for.
  10.  
  11.   2. Each method on the class is doing one of a few things: ASKING the other class to do something with given data OR amending itself OR
  12.      providing specific, structured data to work against without 'demanding' it from the other class by calling specific attributes
  13.      on the class. Grouping is important!
  14.  
  15.   3. Maintains its own state and does not have values set outside of the class itself. If there are changes made, the other class is ASKING THE BASE CLASS
  16.      TO MAKE THE CHANGES, BUT NOT DIRECTLY FORCING IT TO DO SO IN THE SUPERCLASSES LOGIC AS THE CONSTRAINTS ARE SPECIFIC TO THE CLASS AND NOT TO THE IMPLEMENTATION
  17.      AS A WHOLE, more or less.
  18.  
  19. You don't want the 'Animal' class to forefully set any attributes on the 'Food' class ANYHWERE AT ALL. You want to pass data and ask the
  20. Food to change itself if anything, but literally hard-setting attributes from the Animal class is a bad idea.
  21.  
  22. The main idea surrounding OO programming is that you want to have your classes interact with one another and send messages, but you should
  23. NOT have any class directly amending another class's attributes without ASKING it to do so through a method on the class to be amended, as this
  24. standardizes the ways in which the data can go in and out of your class.
  25.  
  26. =end
  27.  
  28. # We will start with the Animal class initially. The animal has some attributes and data associated with it - namely the
  29. # instance variables (@whatever) in the initialize method. This is how we persist data in runtime (when your code is running)
  30. # without having a database to reference against.
  31.  
  32. # The data will not 'save' in the conventional sense, but persists in runtime space until the Ruby code is done executing.
  33.  
  34. class Animal
  35.   attr_reader :species, :name, :diet, :energy, :muscle, :fat
  36.  
  37.   # attr_reader is setting up 'getter' methods for all of the attributes present on initialization.
  38.  
  39.   # This allows you to call Animal.name, Animal.species, Animal.diet, etc to retrieve information about the
  40.   # animal, but does NOT set up 'setter' methods that would allow you to set attributes by doing something like
  41.   # Animal.name = "Carl". We do this because once the animal is created, we simply need to be able to retrieve information as
  42.   # this is cleaner OO, and amendments should be passed in as a chunk of data to be processed if this were a specifically clean implementation of OO.
  43.  
  44.   def initialize(species, name, diet)
  45.     # This thing throws an error if the Animal class is provided with a value for 'diet' that doesn't meet the level
  46.     # of logical abstraction we have established to reach our goal of having an animal that has dietary contraints.
  47.     if ![:omnivore, :herbivore, :carnivore].any?(diet)
  48.       raise ArgumentError, "Please provide a valid diet type for your Animal: i.e. :omnivore, :herbivore, :carnivore"
  49.     end
  50.  
  51.     # Setting up internal data representation and instance-based variable persistence...
  52.     @species = species
  53.     @name = name
  54.  
  55.     @diet = diet
  56.     @hunger = 0
  57.    
  58.     @energy = 10
  59.     @muscle = 10
  60.     @fat = 10
  61.   end
  62.  
  63.   # This is a simple utility method to grab the relevant athletic stats to be returned from methods that amend these attriutes directly
  64.   # and need to be grouped as returned as such for things like control flow, validation, and whatever else we might need the info for
  65.   # so we don't have to keep constructing it in other methods. (See #eat and #process_nutirition for examples of using it).
  66.   def stats
  67.     return {
  68.       energy: @energy,
  69.       muscle: @muscle,
  70.       fat: @fat
  71.     }
  72.   end
  73.  
  74.   # How the animal eats. Notice that I am checking here wether or not the animal can eat the
  75.   # food at all before even attempting to process the nutrition, as there's no reason to run code or make checks against anything if
  76.   # the initial input is invalid to begin with.
  77.  
  78.   # I also made the animal lose energy out of 'annoyance' if they can't eat the food as this is how I wanted to model the reality that these animals exist in.
  79.   def eat(food)
  80.  
  81.     if can_eat.include?(food.type)
  82.       p "#{@name} ate the #{food.name}!"
  83.       process_nutrition!(food.nutrition)
  84.       return stats
  85.     else
  86.       p "#{@name} turns away from the #{food.name}!"
  87.       @energy -= 1
  88.       return stats
  89.     end
  90.   end
  91.  
  92.   # This is encapsulted here as it may be that we want to add nutrition from other items like vitamins, supplements, shots, IV fluid etc. at a later date.
  93.   # This is quite literally how the animal's metabolic system is simulated to work, and the values I am dividing by could just as easily
  94.   # we replaced by metabolite values instead of static intergers. I am keeping it encapsulated because it is, in fact, a different set of logic and
  95.   # actions than simply having the ability to 'eat' a certain food, which could have its own levels of abstraction and ways of doing things
  96.   # independent of how the animal processes the nutrition from the food itself.
  97.   def process_nutrition!(nutrition)
  98.     @energy += ((nutrition[:carbs] / 5) + (nutrition[:sugar] / 3) + (nutrition[:fat] / 12) + (nutrition[:protien] / 2)) / 2
  99.     @hunger -= nutrition.map {|k,v| v }.inject(:+)
  100.     @muscle += nutrition[:protien] / 5
  101.     @fat += nutrition[:fat] / 3
  102.     return stats
  103.   end
  104.  
  105.   # This is simply set up as a reference to ensure that if an animal has a certain diet, they can eat certain foods.
  106.   # I check against this with .include? (see #eat) as it's meant to represent a set of valid data for the Animal to work against.
  107.   def can_eat
  108.     if @diet == :carnivore
  109.       return [:meat]
  110.     elsif @diet == :herbivore
  111.       return [:vegetable, :fruit, :starch, :seed]
  112.     elsif @diet == :omnivore
  113.       return [:vegetable, :fruit, :starch, :seed, :meat]
  114.     end
  115.   end
  116.  
  117. end
  118.  
  119. # The food class is fairly similar, but is designed to be consumed by another class or potentially amended by
  120. # things such as GMO engineering, age, or other environmental factors. This was a specific design decision, as
  121. # I figured it was more reasonable to be able to change the property of a food than an animal for what I was trying to
  122. # accomplish.
  123.  
  124. class Food
  125.   # Getter method setup.
  126.   attr_reader :name
  127.  
  128.   # Getter & Setter method setup.
  129.   attr_accessor :sugar, :fat, :protien, :carbs, :type
  130.  
  131.   # I am passing in a default hash here to ensure that, at the very least, food will have a 0
  132.   # value for all of its attributes. This eliminates the potential for nil errors and will make it so that
  133.   # non-muliplication math logic will maintain identity.
  134.   def initialize(name, type, nutrition = {sugar: 0, fat: 0, protien: 0, carbs: 0})
  135.     @sugar = nutrition[:sugar]
  136.     @fat = nutrition[:fat]
  137.     @protien = nutrition[:protien]
  138.     @carbs = nutrition[:carbs]
  139.  
  140.     @name = name
  141.     @type = type # i.e. :meat, :vegetable, :fruit, :starch, :seed
  142.   end
  143.  
  144.   # Just a simple utility method like Animal.stats. This is atually what the animal is asking for
  145.   # when it needs to process the nutrition gained from the food. We keep it here as there may be other modifiers not specific to the
  146.   # animal that the food needs to amend on itself. Eventually, more transformations in the data may happen here,
  147.   # so its a best to keep this encapsulated as we don't know what we are going to need to make that mechanic work later in development.
  148.   def nutrition
  149.     return {
  150.       sugar: @sugar,
  151.       fat: @fat,
  152.       protien: @protien,
  153.       carbs: @carbs
  154.     }
  155.   end
  156.  
  157.   def supplement(attr, value)
  158.  
  159.   end
  160.  
  161. end
  162.  
  163. # We are just making a bat and feeding it some fruit here. Run the code and see how it works!
  164.  
  165. bat = Animal.new('fruit_bat', "Batty the Fruit Bat", :herbivore)
  166. apple = Food.new('apple', :fruit, {
  167.   sugar: 80,
  168.   fat: 10,
  169.   protien: 5,
  170.   carbs: 40
  171. })
  172. guava = Food.new('guava', :fruit, {
  173.   sugar: 50,
  174.   fat: 20,
  175.   protien: 30,
  176.   carbs: 10
  177. })
  178. p bat.eat(apple)
  179. p bat.eat(guava)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement