Python OOP advice

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Simon Hibbs

    Python OOP advice

    I'm rewriting a design application for a science fiction game. in it
    you design your own starships. Each component has a mass and cost, but
    the mass can either be fixed or it can be expressed as a percentage of
    the tonnage of the overall ship.

    Orriginaly I thought I'd need to have a hull object which contains
    component objects, but the component objects need access to members of
    the hull object (e.g. the hull size) so that looks messy to implement.

    I have defined a base class Component with a class member variable
    'hull_size' so that all components can see the hull size. I've then
    got two child classes called Fixed_Component and Percent _Component
    that implement their mass, mass_percent and cost properties
    appropriately for their type. I've also defined a Hull class which
    also inherits from Component and provides methods for access to the
    hull_size class variable.

    I'm not sure where to go from here. One possibility is to have a Ship
    object containing a list of components, but I'd need to have a way to
    ensure there's only ever one hull object so maybe that shouldn't go in
    the list?

    I think the fact that the hull_size is a class member also means I
    can't ever have an application that loads two designs at the same
    time, because they'd share the same hull_size class variable. Is that
    so, and is there a way round that? I suspect the inheritance model
    will work fine at first, but is too rigid in the long run.

    Is there a way to cleanly implement a parent-child relationship
    between objects that gives child objects limited access to members of
    the parent?

    Simon Hibbs
  • Ben Finney

    #2
    Re: Python OOP advice

    Simon Hibbs <simon.hibbs@gm ail.comwrites:
    Orriginaly I thought I'd need to have a hull object which contains
    component objects, but the component objects need access to members
    of the hull object (e.g. the hull size) so that looks messy to
    implement.
    Was it as messy as this::

    class ShipHull(object ):
    def __init__(self, size):
    self.components = dict()
    self.size = size

    class ShipComponent(o bject):
    def __init__(self, name, hull):
    self.name = name
    self.hull = hull
    I have defined a base class Component with a class member variable
    'hull_size' so that all components can see the hull size.
    It seems to me the hull is an attribute of the component, and the size
    is an attribute of the hull. Why would the hull size be a *class*
    attribute?
    I've then got two child classes called Fixed_Component and Percent
    _Component that implement their mass, mass_percent and cost
    properties appropriately for their type.
    class FixedShipCompon ent(ShipCompone nt):
    def _get_mass(self) :
    return calculation_foo (self.hull.size )
    mass = property(_get_m ass)

    def _get_cost(self) :
    return calculation_bar (self.hull.size )
    cost = property(_get_c ost)

    class PercentShipComp onent(ShipCompo nent):
    def _get_mass(self) :
    return calculation_spa m(self.hull.siz e)
    mass = property(_get_m ass)

    def _get_cost(self) :
    return calculation_egg s(self.hull.siz e)
    cost = property(_get_c ost)
    I've also defined a Hull class which also inherits from Component
    and provides methods for access to the hull_size class variable.
    I don't see why, if a ShipComponent needs to refer to its hull as
    something special, that a ShipHull would subclass ShipComponent.
    Is there a way to cleanly implement a parent-child relationship
    between objects that gives child objects limited access to members
    of the parent?
    Sure; have the child instance grow an attribute referencing the
    parent, preferably by passing it to the initialisation function
    (__init__) of the child.

    --
    \ Rommel: “Don't move, or I'll turn the key on this can of Spam!” |
    `\ —The Goon Show, _Rommel's Treasure_ |
    _o__) |
    Ben Finney

    Comment

    • Paul McGuire

      #3
      Re: Python OOP advice

      On Sep 17, 6:50 am, Simon Hibbs <simon.hi...@gm ail.comwrote:
      I'm rewriting a design application for a science fiction game. in it
      you design your own starships. Each component has a mass and cost, but
      the mass can either be fixed or it can be expressed as a percentage of
      the tonnage of the overall ship.
      >
      Orriginaly I thought I'd need to have a hull object which contains
      component objects, but the component objects need access to members of
      the hull object (e.g. the hull size) so that looks messy to implement.
      >
      I would not put this kind of intelligence into the components.

      I think the issue here is that your Ship container is not really just
      a generic container of ship components, but an assembly with some
      specific requirements (must have 1 and only 1 hull, must have 1 or
      more engines, etc.) and structure. I would create a class called
      ShipDesign that had specific members for those components that have
      special logic attached to them, and then more generic list members for
      collection-ish components.

      Since the hull is such a significant constraint, I would make it an
      initialization argument. I would also put some kind of property on
      hull representing its "capacity" (oh I see, you have something call
      hull_size).

      One way to generalize the fixed-cost vs. percentage-cost components
      would be to have all components implement a compute_load function that
      takes the ShipDesign as an argument. Those that are fixed-cost simply
      return their fixed value, those that are percentage-cost can return
      their percentage of the ShipDesign's hull.hull_size - this leaves the
      door open for other variations on load, that could be based on other
      properties besides the hull size.

      Here's how I envision your ShipDesign class:

      class ShipDesign(obje ct):
      def __init__(self, hull):
      self.hull = hull
      self.engines = []
      self.shields = []
      self.weapons = []
      self.other = []

      def compute_consume d_capacity(self ):
      load = 0
      for itemlist in (self.engines, self.shields,
      self.weapons, self.other)):
      load += sum(item.comput e_load(self)
      for item in itemlist)
      return load

      def add_engine(self ,e):
      engload = e.compute_load( self)
      if engload + self.compute_co nsumed_capacity () >
      self.hull.hull_ size:
      raise ExceededHullCap acityException( )
      if len(self.engine s) == MAXIMUM_ALLOWED _ENGINES:
      raise ExceededMaximum ConfigurationLi mitException()
      self.engines.ap pend(e)

      def set_hull(self, hull):
      if self.compute_co nsumed_capacity () <= hull.hull_size:
      self.hull = hull
      else:
      raise NewHullTooSmall Exception()

      def is_valid(self):
      if not self.engines:
      raise InvalidDesignEx ception("must have at least 1
      engine")
      ...etc...

      class GenericItem(obj ect):
      def __init__(self, name, load):
      self.name = name
      self.load = load
      crewQuarters = GenericItem("Cr ew Quarters", 50)
      disco = GenericItem("Di scotheque", 10)
      ....etc...

      Once you have a valid ShipDesign, you can then use it to construct
      multiple Ship instances.

      Here is how I would work around your "only one hull at a time"
      problem. Define several classes for different kinds of hulls:

      class CheapHull(Hull) :
      capacity = 100
      name = "EconoHull 1000"
      class MediumHull(Hull ):
      capacity = 500
      name = "Mainliner X50"
      class TopOTheLineHull (Hull):
      capacity = 1000
      name = "LuxeMaster 5000"

      and then create ship designs with a CheapHull, a MediumHull, or a
      TopOTheLineHull . In this case, the member variable of the ShipDesign
      is really a class, which you would later use to construct hull
      instances as part of making Ship instances from your ShipDesign.

      class Ship(object):
      def __init__(self, design):
      self.hull = design.hull()
      self.engines = design.engines[:]
      ...etc...

      This way, each Ship will have its own Hull instance, so that you
      can track instance-specific properties, such as damage percentage.

      If you don't want to hard-code the hull types, then you can do
      something similar with instances of a generic Hull class, which you
      would then use as prototypes when constructing Ship instances. Just
      be careful that you don't accidentally have all ships sharing the same
      hull instance!

      -- Paul

      Comment

      • Simon Hibbs

        #4
        Re: Python OOP advice

        Great contributions, thanks both of you. I'm self-tought when it comes
        to Python and OOP and I haven't yet grown an intuitive feel for how to
        do things sensibly.

        Simon

        Comment

        • Bruno Desthuilliers

          #5
          Re: Python OOP advice

          Ben Finney a écrit :
          Simon Hibbs <simon.hibbs@gm ail.comwrites:
          >
          >Orriginaly I thought I'd need to have a hull object which contains
          >component objects, but the component objects need access to members
          >of the hull object (e.g. the hull size) so that looks messy to
          >implement.
          >
          Was it as messy as this::
          >
          class ShipHull(object ):
          def __init__(self, size):
          self.components = dict()
          self.size = size
          >
          class ShipComponent(o bject):
          def __init__(self, name, hull):
          self.name = name
          self.hull = hull
          >
          >I have defined a base class Component with a class member variable
          >'hull_size' so that all components can see the hull size.
          >
          It seems to me the hull is an attribute of the component, and the size
          is an attribute of the hull. Why would the hull size be a *class*
          attribute?
          >
          >I've then got two child classes called Fixed_Component and Percent
          >_Component that implement their mass, mass_percent and cost
          >properties appropriately for their type.
          >
          class FixedShipCompon ent(ShipCompone nt):
          def _get_mass(self) :
          return calculation_foo (self.hull.size )
          mass = property(_get_m ass)
          >
          def _get_cost(self) :
          return calculation_bar (self.hull.size )
          cost = property(_get_c ost)
          >
          class PercentShipComp onent(ShipCompo nent):
          def _get_mass(self) :
          return calculation_spa m(self.hull.siz e)
          mass = property(_get_m ass)
          >
          def _get_cost(self) :
          return calculation_egg s(self.hull.siz e)
          cost = property(_get_c ost)
          Or use the strategy pattern (dummy example, don't have time to read your
          specs !-):

          class FixedMassCostSt rategy(object):
          def get_mass(self, hull):
          return calculation_foo (hull.size)
          def get_cost(self):
          return calculation_bar (hull.size)

          class PercentMassCost Strategy(object ):
          def get_mass(self, hull):
          return calculation_spa m(hull.size)
          def get_cost(self):
          return calculation_egg s(hull.size)


          class ShipComponent(o bject):
          def __init__(self, name, hull, masscost_strate gy):
          self.name = name
          self._hull = hull # implementation detail
          self._strategy = masscost_strate gy

          mass = property(lambda self: self._strategy. get_mass(self._ hull))
          cost = property(lambda self: self._strategy. get_cost(self._ hull))


          Comment

          • Bruno Desthuilliers

            #6
            Re: Python OOP advice

            Simon Hibbs a écrit :
            Great contributions, thanks both of you. I'm self-tought when it comes
            to Python and OOP and I haven't yet grown an intuitive feel for how to
            do things sensibly.
            While part of the OO design patterns are mostly workaround for lack of
            dynamism in languages like C++ or Java, and while pattern abuse is
            certainly not good design, you'd probably learn a lot from the GOF's
            Design Patterns book.
            Simon

            Comment

            Working...