SOLID programming

General discussion for topics related to the FreeBASIC project or its community.
dafhi
Posts: 1640
Joined: Jun 04, 2005 9:51

Re: SOLID programming

Post by dafhi »

just a quick update .. your other example i think is perfect
I simply haven't devoted enough time looking at it.

i will continue to read all examples
PeterHu
Posts: 146
Joined: Jul 24, 2022 4:57

Re: SOLID programming

Post by PeterHu »

paul doe wrote: Sep 27, 2018 12:30 Well, I folded the two examples into one. It's a bit long, but it's straightforward and has some comments to help understand what's going on.

Code: Select all

#include once "fbgfx.bi"
/'
  Some colors (mostly for convenience and readability )
'/
type RGBAColor as ulong

namespace colors
  const red as RGBAColor = rgba( 255, 0, 0, 255 )
  const green as RGBAColor = rgba( 0, 255, 0, 255 )
  const blue as RGBAColor = rgba( 0, 0, 255, 255 )
  const yellow as RGBAColor = rgba( 255, 255, 0, 255 )
  const cyan as RGBAColor = rgba( 0, 255, 255, 255 )
  const magenta as RGBAColor = rgba( 255, 0, 255, 255 )
end namespace

/'
  Interface for all shapes
  
  Here, we'll put all the generic functionality that shapes will
  need to implement. 
'/
type IShape extends Object
  declare virtual destructor()
  
  x as single
  y as single  
  
  protected:
    '' This makes the class behaves as an interface ie. inherit-only
    declare constructor()
end type

constructor IShape()
end constructor

destructor IShape()
end destructor

/'
  Interface for all renderable shapes
'/
type IRenderableShape extends Object
  declare virtual destructor()
  
  declare abstract sub render()
end type

destructor IRenderableShape()
end destructor

/'
  Interface for all shapes that need updating
'/
type IUpdatableShape extends Object
  declare virtual destructor()
  
  declare abstract sub update()
end type

destructor IUpdatableShape()
end destructor

/'
  Interface for all rectangular shapes
'/
type IRectangleShape extends IShape
  declare virtual destructor() override
  
  declare abstract property width() as integer
  declare abstract property height() as integer  
end type

destructor IRectangleShape()
end destructor

/'
  Interface for all circular shapes
'/
type ICircleShape extends IShape
  declare virtual destructor()
  
  declare abstract property radius() as integer
end type

destructor ICircleShape()
end destructor

/'
  A concrete class that represents a rectangular shape
'/
type Rectangle extends IRectangleShape
  public:
    declare constructor( _
      byval as integer, _
      byval as integer, _
      byval as integer, _
      byval as integer )
    declare destructor() override
    
    declare property width() as integer override
    declare property height() as integer override
    
  private:
    declare constructor()
    
    m_width as integer
    m_height as integer
end type

constructor Rectangle()
end constructor

constructor Rectangle( _
  byval newX as integer, _
  byval newY as integer, _
  byval aWidth as integer, _
  byval aHeight as integer )
  
  x = newX
  y = newY
  m_width = iif( aWidth < 1, 1, aWidth )
  m_height = iif( aHeight < 1, 1, aHeight )
end constructor

destructor Rectangle()
end destructor

property Rectangle.width() as integer
  return( m_width )
end property

property Rectangle.height() as integer
  return( m_height )
end property

/'
  A decorator that provides rendering functionality for
  the IRenderableShape interface
  
  This one renders rectangular shapes.
'/
type RenderableRectangle extends IRenderableShape
  public:
    declare constructor( _
      byref as IRectangleShape, _
      byval as RGBAColor )
    declare destructor() override
    
    declare sub render() override
    declare property decorated() as IRectangleShape ptr
    
  private:
    declare constructor()
    
    m_rectangle as IRectangleShape ptr
    m_color as RGBAColor
end type

constructor RenderableRectangle()
end constructor

constructor RenderableRectangle( _
  byref aRectangle as IRectangleShape, _
  byval aColor as RGBAColor )
  
  m_rectangle = @aRectangle
  m_color = aColor
end constructor

destructor RenderableRectangle()
end destructor

property RenderableRectangle.decorated() as IRectangleShape ptr
  return( m_rectangle )
end property

sub RenderableRectangle.render()
  line( m_rectangle->x, m_rectangle->y ) - _
    ( m_rectangle->x + m_rectangle->width - 1, m_rectangle->y + m_rectangle->height - 1 ), _
    m_Color, bf
end sub

/'
  Another decorator, for a rectangle with borders
'/
type RenderableRectangleWithBorder extends IRenderableShape
  public:
    declare constructor( _
      byval as RenderableRectangle ptr, _
      byval as integer, _
      byval as RGBAColor )
    declare destructor() override
    
    declare sub render() override
  
  private:
    declare constructor()
    
    m_renderableRectangle as RenderableRectangle ptr
    m_borderSize as integer
    m_borderColor as RGBAColor
end type

constructor RenderableRectangleWithBorder()
end constructor

constructor RenderableRectangleWithBorder( _
  byval aRenderableRectangle as RenderableRectangle ptr, _
  byval aBorderSize as integer, _
  byval aBorderColor as RGBAColor )
  
  m_renderableRectangle = aRenderableRectangle
  m_borderSize = iif( aBorderSize < 1, 1, aBorderSize )
  m_borderColor = aBorderColor
end constructor

destructor RenderableRectangleWithBorder()
  delete( m_renderableRectangle )
end destructor

sub RenderableRectangleWithBorder.render()
  dim as integer halfSize = m_borderSize / 2
  /'
    Here you call the decorated instance for doing the base
    rendering. This is not necessary, but a 'true' decorator
    always calls the method of the instance it decorates.
  '/
  m_renderableRectangle->render()
  
  line( _
    m_renderableRectangle->decorated->x - halfSize, _
    m_renderableRectangle->decorated->y - halfSize ) - ( _
    m_renderableRectangle->decorated->x + m_renderableRectangle->decorated->width - 1 + halfSize, _
    m_renderableRectangle->decorated->y + m_renderableRectangle->decorated->height - 1 + halfSize ), _
    m_borderColor, b
    
  line( _
    m_renderableRectangle->decorated->x + halfSize, _
    m_renderableRectangle->decorated->y + halfSize ) - ( _
    m_renderableRectangle->decorated->x + m_renderableRectangle->decorated->width - 1 - halfSize, _
    m_renderableRectangle->decorated->y + m_renderableRectangle->decorated->height - 1 - halfSize ), _
    m_borderColor, b
  
  paint( _
    m_renderableRectangle->decorated->x, _
    m_renderableRectangle->decorated->y ), _
    m_borderColor, m_borderColor
end sub

/'
  A concrete class that represents a circle
'/
type Circle extends ICircleShape
  public:
    declare constructor( _
      byval as integer, _
      byval as integer, _
      byval as integer )
    declare destructor() override
    
    declare property radius() as integer override
  
  private:
    declare constructor()
    
    m_radius as integer
end type

constructor Circle()
end constructor

constructor Circle( _
  byval newX as integer, _
  byval newY as integer, _
  byval aRadius as integer )
  
  x = newX
  y = newY
  m_radius = aRadius
end constructor

destructor Circle()
end destructor

property Circle.radius() as integer
  return( m_radius )
end property

/'
  This decorator adds rendering functionality for a circular
  shape.
'/
type RenderableCircle extends IRenderableShape
  public:
    declare constructor( _
      byref as ICircleShape, _
      byval as RGBAColor )
    declare destructor() override
    
    declare sub render() override
    declare property decorated() as ICircleShape ptr
    
  private:
    declare constructor()
    
    m_circle as ICircleShape ptr
    m_color as RGBAColor
end type

constructor RenderableCircle()
end constructor

constructor RenderableCircle( _
  byref aCircle as ICircleShape, _
  byval aColor as RGBAColor )
  
  m_circle = @aCircle
  m_color = aColor
end constructor

destructor RenderableCircle()
end destructor

property RenderableCircle.decorated() as ICircleShape ptr
  return( m_circle )
end property

sub RenderableCircle.render()
  circle( _
    m_circle->x, _
    m_circle->y ), m_circle->radius, m_color, , , , f
end sub

/'
  This decorator adds moving functionality to the shape; in
  this case, to move the shape with the arrow keys.
'/
type MovableWithKeyboard extends IUpdatableShape
  public:
    declare constructor( _
      byref as IShape )
    declare destructor() override
    
    declare sub update() override
  
  private:
    declare constructor()
    
    m_shape as IShape ptr
end type

constructor MovableWithKeyboard()
end constructor

constructor MovableWithKeyboard( _
  byref aShape as IShape )
  
  m_shape = @aShape
end constructor

destructor MovableWithKeyboard()
end destructor

sub MovableWithKeyboard.update()
  if( multikey( fb.SC_UP ) ) then
    m_shape->y -= 1
  end if
  
  if( multikey( fb.SC_DOWN ) ) then
    m_shape->y += 1
  end if

  if( multikey( fb.SC_LEFT ) ) then
    m_shape->x -= 1
  end if
  
  if( multikey( fb.SC_RIGHT ) ) then
    m_shape->x += 1
  end if
end sub

/'
  This decorator adds moving functionality to the shape; in
  this case, to move the shape with the mouse.
'/
type MovableWithMouse extends IUpdatableShape
  public:
    declare constructor( _
      byref as IShape )
    declare destructor() override
    
    declare sub update() override
  
  private:
    declare constructor()
    
    m_shape as IShape ptr
end type

constructor MovableWithMouse()
end constructor

constructor MovableWithMouse( _
  byref aShape as IShape )
  
  m_shape = @aShape
end constructor

destructor MovableWithMouse()
end destructor

sub MovableWithMouse.update()
  dim as integer mx, my
  
  getMouse( mx, my )
  
  m_shape->x = mx
  m_shape->y = my
end sub

/'
  This decorator moves the shape in a random direction, keeping it
  within the given bounds.
'/
type BoundedMovement extends IUpdatableShape
  public:
    declare constructor( _
      byref as IShape, _
      byval as integer, _
      byval as integer, _
      byval as integer, _
      byval as integer )
    declare destructor() override
    
    declare sub update() override
  
  private:
    declare constructor()
    
    m_shape as IShape ptr
    m_top as integer
    m_left as integer
    m_right as integer
    m_bottom as integer
    m_directionX as single
    m_directionY as single
end type

constructor BoundedMovement()
end constructor

constructor BoundedMovement( _
  byref aShape as IShape, _
  byval topLimit as integer, _
  byval leftLimit as integer, _
  byval rightLimit as integer, _
  byval bottomLimit as integer )
  
  m_shape = @aShape
  m_top = topLimit
  m_left = leftLimit
  m_right = rightLimit
  m_bottom = bottomLimit
  m_directionX = ( rnd() * 2.0 ) - 1.0
  m_directionY = ( rnd() * 2.0 ) - 1.0
end constructor

destructor BoundedMovement()
end destructor

sub BoundedMovement.update()
  m_shape->x += m_directionX
  m_shape->y += m_directionY
  
  if( m_shape->x < m_left ) then
    m_shape->x = m_left
    m_directionX = -m_directionX
  end if
  
  if( m_shape->x > m_right ) then
    m_shape->x = m_right
    m_directionX = -m_directionX
  end if

  if( m_shape->y < m_top ) then
    m_shape->y = m_top
    m_directionY = -m_directionY
  end if
  
  if( m_shape->y > m_bottom ) then
    m_shape->y = m_bottom
    m_directionY = -m_directionY
  end if
end sub

/'
  And these represent other layers of the application. They need
  not be functions, they can also be classes, each with its own
  processing model.
'/
sub renderShapes( theRenderableShapes() as IRenderableShape ptr )  
  '' Render shapes
  for i as integer = 0 to ubound( theRenderableShapes )
    theRenderableShapes( i )->render()
  next
end sub

sub updateShapes( theUpdatableShapes() as IUpdatableShape ptr )
  '' Update shapes
  for i as integer = 0 to ubound( theUpdatableShapes )
    theUpdatableShapes( i )->update()
  next
end sub

/'
  Main code
'/
screenRes( 800, 600, 32 )
randomize()

/'
  These objects will be the objects we want to process. By
  themselves, they don't do anything, they just 'represent'
  a shape. I put them into separate lists for clarity (to
  avoid downcasting), but you can also put them into a single
  list. Of course, 'list' here means just an array.
'/
dim as Rectangle rectangles( ... ) = { _
  Rectangle( 300, 200, 100, 100 ), _
  Rectangle( 100, 120, 50, 50 ), _
  Rectangle( 500, 500, 80, 50 ) }

dim as Circle circles( ... ) = { _
  Circle( 200, 50, 30 ), _
  Circle( 500, 400, 50 ), _
  Circle( 50, 500, 100 ) }
  
/'
  These two arrays hold decorated instances of objects, that are
  passed to the appropriate processing functions.
'/
dim as IRenderableShape ptr renderableShapes( ... ) = { _
  new RenderableRectangle( rectangles( 0 ), colors.blue ), _
  new RenderableRectangleWithBorder( _
    new RenderableRectangle( rectangles( 1 ), colors.red ), _
    5, colors.yellow ), _
  new RenderableRectangleWithBorder( _
    new RenderableRectangle( rectangles( 2 ), colors.cyan ), _
    2, colors.magenta ), _
  new RenderableCircle( circles( 0 ), colors.green ), _
  new RenderableCircle( circles( 1 ), colors.yellow ) }

dim as IUpdatableShape ptr updatableShapes( ... ) = { _
  new MovableWithKeyboard( rectangles( 0 ) ), _
  new MovableWithMouse( circles( 0 ) ), _
  new BoundedMovement( _
    rectangles( 1 ), _
    0, 0, 799, 599 ) }
  
/'
  Main loop
  
  Nothing fancy, just render and update shapes. However, do note that
  not all shapes get rendered, and not all shapes get updates; only
  the ones that matter (the ones we specified in the two arrays above)
  are passed to the functions that consume them.
'/
do
  updateShapes( updatableShapes() )
  
  screenLock()
    cls()
    renderShapes( renderableShapes() )
  screenUnlock()
  
  sleep( 1, 1 )
loop until( multikey( fb.SC_ESCAPE ) )

/'
  Dispose of the explicitly created decorators
'/
for i as integer = 0 to ubound( renderableShapes )
  delete( renderableShapes( i ) )
next

for i as integer = 0 to ubound( updatableShapes )
  delete( updatableShapes( i ) )
next
Use the arrow keys to move a shape and the mouse to move another. There's a shape that moves automatically, and two shapes that don't move at all.

Well, that should wrap some basic concepts about encapsulation and interfaces. Do UML Class Diagrams help anybody to better grasp the concepts? If it does, I'll post the class diagram for this snippet also.
Great!!Can't imagine before FB can do this!!! direct access hardware as powerful as c,mucher cleaner code; can create classes(type) instance both on the stack & on the heap,interface...Can't imagine what feature FB is lack of,template?

Code: Select all

type RenderableRectangleWithBorder extends IRenderableShape
  public:
    declare constructor( _
      byval as RenderableRectangle ptr, _
byval---is this a typo,should be byref instead? or it is byval?I ran both cases and found the program produce exactly the same result.

BTW,as the manual said:

Code: Select all

In the -lang fb dialect, numeric parameters are passed Byval by default. Strings and UDTs are passed Byref by default.
so can byval and byref be omitted?Having them seems a bit verbose.What's the best practice should I follow?
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: SOLID programming

Post by paul doe »

PeterHu wrote: Apr 03, 2023 3:50 Great!!Can't imagine before FB can do this!!! direct access hardware as powerful as c,mucher cleaner code; can create classes(type) both on stack & on heap,interface...Can't imagine what feature FB is lack of,template?
...
Effectively. FreeBasic can't yet define anything like a 'proper' interface (like those seen in C#/Java), nor can it define generic classes (templates).

Code: Select all

type RenderableRectangleWithBorder extends IRenderableShape
  public:
    declare constructor( _
      byval as RenderableRectangle ptr, _
byval---is this a typo,should be byref instead? or it is byval?I ran both cases and found the program produce exactly the same result.

BTW,as the manual said:

Code: Select all

In the -lang fb dialect, numeric parameters are passed Byval by default. Strings and UDTs are passed Byref by default.
so can byval and byref be omitted?Having them seems a bit verbose.What's the best practice should I follow?
It's a pointer so it should be byval (if you omit the specification, it's passed byval by default).

It can be omitted if you want. I've changed my own style to omit it (that's old code), so it's more clear which parameters get passed byref (so it's easier to pinpoint which parameters are meant to return values from a function).

If we were talking about 'best practices', I'd say that your style should reflect your intent; that is, if another coder reads your code, he should be reasonably sure what your intent was when you wrote the code, provided he's experienced enough (comments should also be in place to clarify if needed; never underestimate the importance of good comments).
Post Reply