Thread overview
best approach to code hierarchical classes ?
Jun 08, 2021
someone
Jun 08, 2021
someone
Jun 08, 2021
Paul Backus
Jun 08, 2021
someone
Jun 09, 2021
someone
June 08, 2021

Consider the following code in what I used nested-classes for the first time within D:

import std.string;
import std.stdio;

class classComputers {

   classComputers lhs;
   classComputers rhs;

   int opApply(int delegate(classComputer) dg) { /// boilerplate code to handle the class's default collection

      int lintResult = 0; /// must find a better name

		foreach (lobjComputer; computers) { /// looping over the computers starting in current node

			lintResult = dg(lobjComputer); /// passing single object to the loop body

			if (lintResult != 0) { break; }

		}

      if (lintResult != 0 && lhs ! is null) { lintResult = lhs.opApply(dg); } /// recursing child nodes
      if (lintResult != 0 && rhs ! is null) { lintResult = rhs.opApply(dg); } /// recursing child nodes

      return lintResult;

   }

   public classComputer[] computers; alias computers this; /// ie: default property

   private string pstrNetwork;

   final @property string network() { return this.pstrNetwork; }
   final @property void network(in string lstrNetwork) { this.pstrNetwork = lstrNetwork.strip(); }

   this(
      string lstrNetwork
      ) {

      this.network = lstrNetwork;

   }

   class classComputer {

      private string pstrName;

      final @property string name() { return this.pstrName; }
      final @property void name(in string lstrName) { this.pstrName = lstrName.strip(); }

      this(
         string lstrName
         ) {

         this.name = lstrName;

      }

   }

}

void main (

   ) {

   classComputers lobjComputers = new classComputers(r"lab"c); /// ie: the lab network

   lobjComputers ~= lobjComputers.new classComputer(r"dell"c);
   lobjComputers ~= lobjComputers.new classComputer(r"ibm"c);
   lobjComputers ~= lobjComputers.new classComputer(r"apple"c);
   lobjComputers[1].name = r"lenovo"c;

   foreach(lobjComputer; lobjComputers) { writeln(r"["c ~ lobjComputer.outer.network ~ r" network has "c ~ lobjComputer.name ~ r"]"c); }

}

As expected, the above code produces:

[lab network has dell]
[lab network has lenovo]
[lab network has apple]

Now think of wider hierarchical structures, let's say 6~9 to begin with or greater; eg: computers.computer.devices.firmware... and so on.

Coding such scenario with nested classes will produce a huge code base within the same module. My first approach, obviously, was to put each class in its own module and in main() I instantiate the primary one and the child objects/classes are instantiated either directly in main or say, by each class' own add()/remove() methods. This works as expected. However, I can't manage to traverse the tree from main() like something.parent.somethingelse (like I can do with outer for the nested classes example I gave you) nor I am able to access parent members within the classes own bodies.

Are there alternatives to nested classes for such scenarios ?

June 08, 2021

On Tuesday, 8 June 2021 at 00:54:41 UTC, someone wrote:

>

Are there alternatives to nested classes for such scenarios ?

Self-reply: I created two files for classComputers and classComputer and I replaced the nested-classComputer code within classComputers with:

import classComputers;

But it won't go:

Error: undefined identifier classComputer in module classComputers, did you mean class classComputers?

June 08, 2021

On Tuesday, 8 June 2021 at 01:17:05 UTC, someone wrote:

>

On Tuesday, 8 June 2021 at 00:54:41 UTC, someone wrote:

>

Are there alternatives to nested classes for such scenarios ?

Self-reply: I created two files for classComputers and classComputer and I replaced the nested-classComputer code within classComputers with:

import classComputers;

But it won't go:

Error: undefined identifier classComputer in module classComputers, did you mean class classComputers?

Your module and class are both named classComputers, with an s at the end. You should change one of the to have a different name so that there's no ambiguity.

June 08, 2021

On Tuesday, 8 June 2021 at 02:05:27 UTC, Paul Backus wrote:

>

Your module and class are both named classComputers, with an s at the end. You should change one of the to have a different name so that there's no ambiguity.

dmd output:

./dm.d(49): Error: undefined identifier classComputer in module dmclassescomputers, did you mean class classComputers?
./dm.d(50): Error: undefined identifier classComputer in module dmclassescomputers, did you mean class classComputers?
./dm.d(51): Error: undefined identifier classComputer in module dmclassescomputers, did you mean class classComputers?
./dm.d(53): Error: no property outer for type dmclassescomputer.classComputer

file [dm.d] as following:

import dmclassescomputers = dmclassescomputers;
///import dmclassescomputer = dmclassescomputer;

import std.string;
import std.stdio;

void main (

   ) {

   auto lobjComputers = new dmclassescomputers.classComputers(r"lab"c);
   lobjComputers ~= lobjComputers.new dmclassescomputers.classComputer(r"dell"c); /// <- line 49
   lobjComputers ~= lobjComputers.new dmclassescomputers.classComputer(r"ibm"c);
   lobjComputers ~= lobjComputers.new dmclassescomputers.classComputer(r"apple"c);
   lobjComputers[1].name = r"lenovo"c;

   foreach(lobjComputer; lobjComputers) { writeln(r"["c ~ lobjComputer.outer.network ~ r" network has "c ~ lobjComputer.name ~ r"]"c); }

}

file [dmclassescomputers.d] as following:

module dmclassescomputers;

import std.string;

class classComputers {

   classComputers lhs;
   classComputers rhs;

   int opApply(int delegate(dmclassescomputer.classComputer) dg) { /// boilerplate code to handle the class's default collection

      int lintResult = 0; /// must find a better name

		foreach (lobjComputer; computers) { /// looping over the computers starting in current node

			lintResult = dg(lobjComputer); /// passing single object to the loop body

			if (lintResult != 0) { break; }

		}

      if (lintResult != 0 && lhs ! is null) { lintResult = lhs.opApply(dg); } /// recursing child nodes
      if (lintResult != 0 && rhs ! is null) { lintResult = rhs.opApply(dg); } /// recursing child nodes

      return lintResult;

   }

   public dmclassescomputer.classComputer[] computers; alias computers this; /// ie: default property

   private string pstrNetwork;

   final @property string network() { return this.pstrNetwork; }
   final @property void network(in string lstrNetwork) { this.pstrNetwork = lstrNetwork.strip(); }

   this(
      string lstrNetwork
      ) {

      this.network = lstrNetwork;

   }

   import dmclassescomputer;

}

file [dmclassescomputer.d] as following:

module dmclassescomputer;

import std.string;

class classComputer {

   private string pstrName;

   final @property string name() { return this.pstrName; }
   final @property void name(in string lstrName) { this.pstrName = lstrName.strip(); }

   this(
      string lstrName
      ) {

      this.name = lstrName;

   }

}
June 09, 2021

On Tuesday, 8 June 2021 at 02:37:44 UTC, someone wrote:

>

On Tuesday, 8 June 2021 at 02:05:27 UTC, Paul Backus wrote:

>

Your module and class are both named classComputers, with an s at the end. You should change one of the to have a different name so that there's no ambiguity.

Although I am still not being able to solve the import issue for the child code on my nested-classes (low priority by the time being), I have now a more pressing issue.

But first and foremost: almost sure you'll find my code style noisy given the heavily-used attribue/function properties, but this is solely to remind me how things work in a new language, what will be obvious to an experienced D developer (default attributes) it is not obvious to me right now ... spare me the style, will you ?

I moved all the boilerplate code to manage the collection to classCollectionItems which also has a nested child classCollectionItem (since I'll be widely using them as the basis for hierarchical class models) and from then on I inherit the class on my final classComputers/classComputer code which is my first test-bed app in D.

Before moving the code to the new generic abstract classes the code below worked flawlessly, and still works albeit for a minor detail:

I now have an issue accessing the collection (previously defined in the base classCollectionItems) through my derived classComputers class: please, look for the comment block marked with +++ below.

The base classCollectionItems will always have a collection named items, and each derived class I will make from it should have a reference to it renamed accordingly; eg: computers in the case of my classComputers class. Later I will plan to make an interface to hide all the inner details and hide the items property of the base class once and for all, leaving each new derived class' own renamed property publicly accessible). So when the issue first appeared on my new refactored code I started playing with alias to no avail -and even making a new computers property initialized with items (but then I figured it out that this will be copying the array -not referencing it, so this will be a no-go).

Some advice please ?

#!/bin/dmd

module dmclassescomputers;

import std.string;

abstract private class classCollectionItems {

   classCollectionItems lhs;
   classCollectionItems rhs;

   int opApply(int delegate(classCollectionItem) dg) { /// boilerplate code to handle the class's default collection

      int lintResult = 0; /// must find a better name

		foreach (lobjItem; items) { /// looping over the computers starting in current node

			lintResult = dg(lobjItem); /// passing single object to the loop body

			if (lintResult != 0) { break; }

		}

      if (lintResult != 0 && lhs ! is null) { lintResult = lhs.opApply(dg); } /// recursing child nodes
      if (lintResult != 0 && rhs ! is null) { lintResult = rhs.opApply(dg); } /// recursing child nodes

      return lintResult;

   }

   public @property classCollectionItem[] items; alias items this; /// ie: default property

   final public @property const long count0() { return this.items.empty ? 0L : this.items.length - 1L; }
   final public @property const long count1() { return this.items.empty ? 0L : this.items.length; }
   final public @property const long count() { return this.count1; }
   final public @property const bool empty() { return this.items.empty; }

   abstract public bool add(in string lstrID) { return false; }
   abstract public bool remove(in string lstrID) { return false; }
   abstract public bool removeAll() { return false; }

   abstract class classCollectionItem {

      private ulong pintPosition0 = 0L; /// keep in mind that array positions are zero-based

      final public @property const ulong position0() { return this.pintPosition0; }
      final public @property const ulong position1() { return this.pintPosition0 + 1L; }

      final public @property const ulong countAbove() { return this.outer.items.empty ? 0L : this.pintPosition0; }
      final public @property const ulong countBelow() { return this.outer.items.empty ? 0L : this.outer.length - this.pintPosition0 - 1L; }

      /// eg: for position0=0 → countAbove=0=(position0=0) & countBelow=2=(length=3)-(position0=0)-1
      /// eg: for position0=1 → countAbove=1=(position0=1) & countBelow=1=(length=3)-(position0=1)-1
      /// eg: for position0=2 → countAbove=2=(position0=2) & countBelow=0=(length=3)-(position0=2)-1

      final public @property classCollectionItem first() { return this.outer.items.empty ? null : this.outer.items[0L]; }
      final public @property classCollectionItem previous() { return this.outer.items.empty || this.countAbove == 0L ? null : this.outer.items[this.pintPosition0 - 1L]; }
      final public @property classCollectionItem next() { return this.outer.items.empty || this.countBelow == 0L ? null : this.outer.items[this.position0 + 1L]; }
      final public @property classCollectionItem last() { return this.outer.items.empty ? null : this.outer.items[this.outer.items.length - 1L]; }

   }

}

final private class classComputers : classCollectionItems {

   private string pstrNetwork;
   final public @property const string network() { return this.pstrNetwork; }
   final public @property void network(in string lstrNetwork) { this.pstrNetwork = lstrNetwork.strip(); }

   this(
      in string lstrNetwork = null
      ) {

      if (lstrNetwork ! is null) { this.network = lstrNetwork; }

   }

   /// +++ Error: no property `ID` for type `dmclassescomputers.classCollectionItems.classCollectionItem`

   /// +++ alias computers = this;
   /// +++ alias computers = this.items;
   /// +++ alias computers = classCollectionItems;
   /// +++ alias computers = classCollectionItems.items; alias computers this; /// it seems to me something like this should be the correct one but obviously it is not :(
   /// +++ alias computers = classCollectionItems.items[];
   /// +++ public @property classCollectionItem[] computers = this.items;

   /// +++ checked: https://dlang.org/spec/declaration.html#alias
   /// +++ checked: http://ddili.org/ders/d.en/alias_this.html

   final public bool add(in string lstrID, in string lstrName) { return false; }

   override final public bool add(in string lstrID) { return false; }

   override final public bool remove(in string lstrID) { return false; }

   override final public bool removeAll() { return false; }

   final class classComputer : classCollectionItem { /// should: import dmclassescomputer;

      private string pstrID; final @property const string ID() { return this.pstrID; }
      private string pstrName; final @property const string name() { return this.pstrName; }
      final public @property void name(in string lstrName) { this.pstrName = lstrName.strip(); }

      this(
         in string lstrID,
         in string lstrName = null
         ) {

         this.pintPosition0 = this.outer.items.length;

         if (lstrID ! is null) { this.pstrID = lstrID.strip(); }
         if (lstrName ! is null) { this.pstrName = lstrName.strip(); }

      }

   }

   unittest {

      dmclassescomputers.classComputers lobjComputers;

      lobjComputers = new dmclassescomputers.classComputers; assert(lobjComputers.network is null);
      lobjComputers = new dmclassescomputers.classComputers(null); assert(lobjComputers.network is null);
      lobjComputers = new dmclassescomputers.classComputers(r"lab"c); assert(lobjComputers.network == r"lab"c);

      assert(lobjComputers.count0 == 0);
      assert(lobjComputers.count1 == 0);
      assert(lobjComputers.count == 0);

      bool lbolComputers = false;

      foreach(lobjComputer; lobjComputers) { /// testing access to the empty collection: should never get within the body of the loop

         lbolComputers = true;

      }

      assert(lbolComputers == false);

      lobjComputers ~= lobjComputers.new classComputer(r"WS1"c, r"dell"c); /// adding the first computer

      assert(lobjComputers.count == 1L);
      assert(lobjComputers.count1 == 1L);
      assert(lobjComputers.count0 == 0L);

      with (lobjComputers[0L]) {

         assert(countAbove == 0L);
         assert(countBelow == 0L);

         assert(first ! is null && first.ID == r"WS1"c);
         assert(previous is null);
         assert(next is null);
         assert(last ! is null && last.ID == r"WS1"c);

      }

      lobjComputers ~= lobjComputers.new classComputer(r"WS2"c, r"ibm"c); /// adding a second computer

      assert(lobjComputers.count == 2L);
      assert(lobjComputers.count1 == 2L);
      assert(lobjComputers.count0 == 1L);

      with (lobjComputers[1L]) {

         assert(countAbove == 1L);
         assert(countBelow == 0L);

         assert(first ! is null && first.ID == r"WS1"c);
         assert(previous ! is null && previous.ID == r"WS1"c);
         assert(next is null);
         assert(last ! is null && last.ID == r"WS2"c);

      }

      lobjComputers ~= lobjComputers.new classComputer(r"WS3"c, r"apple"c); /// adding a third computer

      assert(lobjComputers.count == 3L);
      assert(lobjComputers.count1 == 3L);
      assert(lobjComputers.count0 == 2L);

      with (lobjComputers[2L]) {

         assert(countAbove == 2L);
         assert(countBelow == 0L);

         assert(first ! is null && first.ID == r"WS1"c);
         assert(previous ! is null && previous.ID == r"WS2"c);
         assert(next is null);
         assert(last ! is null && last.ID == r"WS3"c);

      }

      with (lobjComputers[1L]) {

         assert(countAbove == 1L);
         assert(countBelow == 1L);

         assert(first ! is null && first.ID == r"WS1"c);
         assert(previous ! is null && previous.ID == r"WS1"c);
         assert(next ! is null && next.ID == r"WS3"c);
         assert(last ! is null && last.ID == r"WS3"c);

      }

      with (lobjComputers[0L]) {

         assert(countAbove == 0L);
         assert(countBelow == 2L);

         assert(first ! is null && first.ID == r"WS1"c);
         assert(previous is null);
         assert(next ! is null && next.ID == r"WS2"c);
         assert(last ! is null && last.ID == r"WS3"c);

      }

      lobjComputers[1L].name = r"lenovo"c; assert(lobjComputers[1L].name == r"lenovo"c);

      assert(lobjComputers[0L].position1 == 1L);
      assert(lobjComputers[1L].position1 == 2L);
      assert(lobjComputers[2L].position1 == 3L);

      assert(lobjComputers[0L].position0 == 0L);
      assert(lobjComputers[1L].position0 == 1L);
      assert(lobjComputers[2L].position0 == 2L);

      foreach(lobjComputer; lobjComputers) { /// testing access to the collection

         assert(lobjComputer.outer.network == r"lab"c); /// testing parent class' properties

      }

   }

}