House plan design (Head First C#)












16












$begingroup$


I've been working through the book Head First C# and this is an exercise from chapter 7, which is about interfaces and abstract classes.



Note that the class design was mandated by the authors, I'm open to suggestions to improve.



I'd like a review on any and all aspects. My code ended up quite a bit different than the book's solution, because it's a few years old and I think "holds back" on newer C#/.NET features and syntax.



In a shared code base, I would include that XML documentation, but since this is just local code for learning, I didn't. Any and all feedback appreciated.





The authors' published implementation is here on GitHub. The idea is to implement a floor plan that can be explored, with room connections and doors to the outside. It's done through a WinForms application.



Here is a picture of the plan from the book. Sorry, couldn't find a screenshot.



floorplan



The app looks like this:



app





Location.cs



using System;

namespace House
{
abstract class Location
{
public Location(string name) => Name = name;

public string Name { get; private set; }
public Location Exits;

public virtual string Description
{
get
{
string description = $"You're standing in the {Name}. You see exits to the following places: rn";
foreach (Location exit in Exits)
{
description += $"— {exit.Name}";
if (exit != Exits[Exits.Length - 1])
{
description += "rn";
}
}
return description;
}
}
}
}


Room.cs



using System;

namespace House
{
class Room : Location
{
public Room(string name, string decoration)
: base(name)
{
Decoration = decoration;
}

private string Decoration { get; set; }

public override string Description => $"You see {Decoration}. {base.Description} ";
}
}


Outside.cs



using System;

namespace House
{
class Outside : Location
{
public Outside(string name, bool hot)
: base(name)
{
Hot = hot;
}

private bool Hot { get; }

override public string Description => Hot
? "It's very hot here. " + base.Description
: base.Description;
}
}




IHasInteriorDoor.cs



using System;

namespace House
{
interface IHasExteriorDoor
{
string DoorDescription { get; }
Location DoorLocation { get; }
}
}




OutsideWithDoor.cs



using System;

namespace House
{
class OutsideWithDoor : Outside, IHasExteriorDoor
{
public OutsideWithDoor(string name, bool hot, string doorDescription)
: base(name, hot)
{
DoorDescription = doorDescription;
}

public string DoorDescription { get; private set; }

public Location DoorLocation { get; set; }

public override string Description => $"{base.Description}rn You see {DoorDescription} to go inside.";
}
}


RoomWithDoor.cs



using System;

namespace House
{
class RoomWithDoor : Room, IHasExteriorDoor
{
public RoomWithDoor(string name, string decoration, string doorDescription)
: base(name, decoration)
{
DoorDescription = doorDescription;
}

public string DoorDescription { get; private set; }

public Location DoorLocation { get; set; }
}
}




And here is the WinForms to make it work. Leaving out IDE generated code.



ExploreTheHouseForm.cs



using System;
using System.Windows.Forms;

namespace House
{
public partial class ExploreTheHouseForm : Form
{
Location currentLocation;

RoomWithDoor livingRoom;
RoomWithDoor kitchen;
Room diningRoom;
OutsideWithDoor frontYard;
OutsideWithDoor backYard;
Outside garden;

public ExploreTheHouseForm()
{
InitializeComponent();
CreateObjects();
MoveToLocation(livingRoom);
}

private void CreateObjects()
{
// Configure the locations
livingRoom = new RoomWithDoor("living room", "an antique carpet", "an oak door with a brass knob");
kitchen = new RoomWithDoor("kitchen", "stainless steel appliances", "a screen door");
diningRoom = new Room("dining room", "a crystal chandelier");
frontYard = new OutsideWithDoor("front yard", false, livingRoom.DoorDescription);
backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
garden = new Outside("garden", false);

// Configure the exits
livingRoom.Exits = new Location { diningRoom };
kitchen.Exits = new Location { diningRoom };
diningRoom.Exits = new Location { livingRoom, kitchen };
frontYard.Exits = new Location { backYard, garden };
backYard.Exits = new Location { frontYard, garden };
garden.Exits = new Location { frontYard, backYard };

// Configure exterior doors
livingRoom.DoorLocation = frontYard;
frontYard.DoorLocation = livingRoom;
kitchen.DoorLocation = backYard;
backYard.DoorLocation = kitchen;
}

private void MoveToLocation(Location location)
{
currentLocation = location;
ExitsComboBox.Items.Clear();
foreach (Location exit in location.Exits)
{
ExitsComboBox.Items.Add(exit.Name);
}
ExitsComboBox.SelectedIndex = 0;
DescriptionTextBox.Text = currentLocation.Description;
ShowGoThroughExteriorDoorButton(currentLocation);
}

private void ShowGoThroughExteriorDoorButton(Location location)
{
if (location is IHasExteriorDoor)
{
GoThroughExteriorDoorButton.Visible = true;
return;
}
GoThroughExteriorDoorButton.Visible = false;
}

private void GoHereButton_Click(object sender, EventArgs e)
{
MoveToLocation(currentLocation.Exits[ExitsComboBox.SelectedIndex]);
}

private void GoThroughExteriorDoorButton_Click(object sender, EventArgs e)
{
IHasExteriorDoor locationWithExteriorDoor = currentLocation as IHasExteriorDoor;
MoveToLocation(locationWithExteriorDoor.DoorLocation);
}
}
}









share|improve this question











$endgroup$












  • $begingroup$
    The winforms screen gives the option to go through the exterior door, but whatever is behind the exterior door doesn't get listed in the available places? What happens if the kitchen had multiple exterior doors to multiple places? What if the entire house was just a kitchen?
    $endgroup$
    – Mast
    yesterday






  • 3




    $begingroup$
    If you are interested in the general problem of how to write text-based adventures, I encourage you to look into Inform7, an exceedingly clever language for writing such adventures. If you're interested in learning about virtual machines for implementing such adventures, I did a series on my blog a few years ago about the Z-machine.
    $endgroup$
    – Eric Lippert
    17 hours ago






  • 1




    $begingroup$
    As a bonus, on a Z-machine code for this problem will occupy less than a kilobyte. You won't be able to compile the above code into a .NET assembly that takes up that much. (This is another way of saying "in Ye Olde Days we didn't have no interfaces and abstract classes". However, we were also much more likely to be eaten by a grue, so it all evens out in the end.)
    $endgroup$
    – Jeroen Mostert
    2 hours ago


















16












$begingroup$


I've been working through the book Head First C# and this is an exercise from chapter 7, which is about interfaces and abstract classes.



Note that the class design was mandated by the authors, I'm open to suggestions to improve.



I'd like a review on any and all aspects. My code ended up quite a bit different than the book's solution, because it's a few years old and I think "holds back" on newer C#/.NET features and syntax.



In a shared code base, I would include that XML documentation, but since this is just local code for learning, I didn't. Any and all feedback appreciated.





The authors' published implementation is here on GitHub. The idea is to implement a floor plan that can be explored, with room connections and doors to the outside. It's done through a WinForms application.



Here is a picture of the plan from the book. Sorry, couldn't find a screenshot.



floorplan



The app looks like this:



app





Location.cs



using System;

namespace House
{
abstract class Location
{
public Location(string name) => Name = name;

public string Name { get; private set; }
public Location Exits;

public virtual string Description
{
get
{
string description = $"You're standing in the {Name}. You see exits to the following places: rn";
foreach (Location exit in Exits)
{
description += $"— {exit.Name}";
if (exit != Exits[Exits.Length - 1])
{
description += "rn";
}
}
return description;
}
}
}
}


Room.cs



using System;

namespace House
{
class Room : Location
{
public Room(string name, string decoration)
: base(name)
{
Decoration = decoration;
}

private string Decoration { get; set; }

public override string Description => $"You see {Decoration}. {base.Description} ";
}
}


Outside.cs



using System;

namespace House
{
class Outside : Location
{
public Outside(string name, bool hot)
: base(name)
{
Hot = hot;
}

private bool Hot { get; }

override public string Description => Hot
? "It's very hot here. " + base.Description
: base.Description;
}
}




IHasInteriorDoor.cs



using System;

namespace House
{
interface IHasExteriorDoor
{
string DoorDescription { get; }
Location DoorLocation { get; }
}
}




OutsideWithDoor.cs



using System;

namespace House
{
class OutsideWithDoor : Outside, IHasExteriorDoor
{
public OutsideWithDoor(string name, bool hot, string doorDescription)
: base(name, hot)
{
DoorDescription = doorDescription;
}

public string DoorDescription { get; private set; }

public Location DoorLocation { get; set; }

public override string Description => $"{base.Description}rn You see {DoorDescription} to go inside.";
}
}


RoomWithDoor.cs



using System;

namespace House
{
class RoomWithDoor : Room, IHasExteriorDoor
{
public RoomWithDoor(string name, string decoration, string doorDescription)
: base(name, decoration)
{
DoorDescription = doorDescription;
}

public string DoorDescription { get; private set; }

public Location DoorLocation { get; set; }
}
}




And here is the WinForms to make it work. Leaving out IDE generated code.



ExploreTheHouseForm.cs



using System;
using System.Windows.Forms;

namespace House
{
public partial class ExploreTheHouseForm : Form
{
Location currentLocation;

RoomWithDoor livingRoom;
RoomWithDoor kitchen;
Room diningRoom;
OutsideWithDoor frontYard;
OutsideWithDoor backYard;
Outside garden;

public ExploreTheHouseForm()
{
InitializeComponent();
CreateObjects();
MoveToLocation(livingRoom);
}

private void CreateObjects()
{
// Configure the locations
livingRoom = new RoomWithDoor("living room", "an antique carpet", "an oak door with a brass knob");
kitchen = new RoomWithDoor("kitchen", "stainless steel appliances", "a screen door");
diningRoom = new Room("dining room", "a crystal chandelier");
frontYard = new OutsideWithDoor("front yard", false, livingRoom.DoorDescription);
backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
garden = new Outside("garden", false);

// Configure the exits
livingRoom.Exits = new Location { diningRoom };
kitchen.Exits = new Location { diningRoom };
diningRoom.Exits = new Location { livingRoom, kitchen };
frontYard.Exits = new Location { backYard, garden };
backYard.Exits = new Location { frontYard, garden };
garden.Exits = new Location { frontYard, backYard };

// Configure exterior doors
livingRoom.DoorLocation = frontYard;
frontYard.DoorLocation = livingRoom;
kitchen.DoorLocation = backYard;
backYard.DoorLocation = kitchen;
}

private void MoveToLocation(Location location)
{
currentLocation = location;
ExitsComboBox.Items.Clear();
foreach (Location exit in location.Exits)
{
ExitsComboBox.Items.Add(exit.Name);
}
ExitsComboBox.SelectedIndex = 0;
DescriptionTextBox.Text = currentLocation.Description;
ShowGoThroughExteriorDoorButton(currentLocation);
}

private void ShowGoThroughExteriorDoorButton(Location location)
{
if (location is IHasExteriorDoor)
{
GoThroughExteriorDoorButton.Visible = true;
return;
}
GoThroughExteriorDoorButton.Visible = false;
}

private void GoHereButton_Click(object sender, EventArgs e)
{
MoveToLocation(currentLocation.Exits[ExitsComboBox.SelectedIndex]);
}

private void GoThroughExteriorDoorButton_Click(object sender, EventArgs e)
{
IHasExteriorDoor locationWithExteriorDoor = currentLocation as IHasExteriorDoor;
MoveToLocation(locationWithExteriorDoor.DoorLocation);
}
}
}









share|improve this question











$endgroup$












  • $begingroup$
    The winforms screen gives the option to go through the exterior door, but whatever is behind the exterior door doesn't get listed in the available places? What happens if the kitchen had multiple exterior doors to multiple places? What if the entire house was just a kitchen?
    $endgroup$
    – Mast
    yesterday






  • 3




    $begingroup$
    If you are interested in the general problem of how to write text-based adventures, I encourage you to look into Inform7, an exceedingly clever language for writing such adventures. If you're interested in learning about virtual machines for implementing such adventures, I did a series on my blog a few years ago about the Z-machine.
    $endgroup$
    – Eric Lippert
    17 hours ago






  • 1




    $begingroup$
    As a bonus, on a Z-machine code for this problem will occupy less than a kilobyte. You won't be able to compile the above code into a .NET assembly that takes up that much. (This is another way of saying "in Ye Olde Days we didn't have no interfaces and abstract classes". However, we were also much more likely to be eaten by a grue, so it all evens out in the end.)
    $endgroup$
    – Jeroen Mostert
    2 hours ago
















16












16








16


2



$begingroup$


I've been working through the book Head First C# and this is an exercise from chapter 7, which is about interfaces and abstract classes.



Note that the class design was mandated by the authors, I'm open to suggestions to improve.



I'd like a review on any and all aspects. My code ended up quite a bit different than the book's solution, because it's a few years old and I think "holds back" on newer C#/.NET features and syntax.



In a shared code base, I would include that XML documentation, but since this is just local code for learning, I didn't. Any and all feedback appreciated.





The authors' published implementation is here on GitHub. The idea is to implement a floor plan that can be explored, with room connections and doors to the outside. It's done through a WinForms application.



Here is a picture of the plan from the book. Sorry, couldn't find a screenshot.



floorplan



The app looks like this:



app





Location.cs



using System;

namespace House
{
abstract class Location
{
public Location(string name) => Name = name;

public string Name { get; private set; }
public Location Exits;

public virtual string Description
{
get
{
string description = $"You're standing in the {Name}. You see exits to the following places: rn";
foreach (Location exit in Exits)
{
description += $"— {exit.Name}";
if (exit != Exits[Exits.Length - 1])
{
description += "rn";
}
}
return description;
}
}
}
}


Room.cs



using System;

namespace House
{
class Room : Location
{
public Room(string name, string decoration)
: base(name)
{
Decoration = decoration;
}

private string Decoration { get; set; }

public override string Description => $"You see {Decoration}. {base.Description} ";
}
}


Outside.cs



using System;

namespace House
{
class Outside : Location
{
public Outside(string name, bool hot)
: base(name)
{
Hot = hot;
}

private bool Hot { get; }

override public string Description => Hot
? "It's very hot here. " + base.Description
: base.Description;
}
}




IHasInteriorDoor.cs



using System;

namespace House
{
interface IHasExteriorDoor
{
string DoorDescription { get; }
Location DoorLocation { get; }
}
}




OutsideWithDoor.cs



using System;

namespace House
{
class OutsideWithDoor : Outside, IHasExteriorDoor
{
public OutsideWithDoor(string name, bool hot, string doorDescription)
: base(name, hot)
{
DoorDescription = doorDescription;
}

public string DoorDescription { get; private set; }

public Location DoorLocation { get; set; }

public override string Description => $"{base.Description}rn You see {DoorDescription} to go inside.";
}
}


RoomWithDoor.cs



using System;

namespace House
{
class RoomWithDoor : Room, IHasExteriorDoor
{
public RoomWithDoor(string name, string decoration, string doorDescription)
: base(name, decoration)
{
DoorDescription = doorDescription;
}

public string DoorDescription { get; private set; }

public Location DoorLocation { get; set; }
}
}




And here is the WinForms to make it work. Leaving out IDE generated code.



ExploreTheHouseForm.cs



using System;
using System.Windows.Forms;

namespace House
{
public partial class ExploreTheHouseForm : Form
{
Location currentLocation;

RoomWithDoor livingRoom;
RoomWithDoor kitchen;
Room diningRoom;
OutsideWithDoor frontYard;
OutsideWithDoor backYard;
Outside garden;

public ExploreTheHouseForm()
{
InitializeComponent();
CreateObjects();
MoveToLocation(livingRoom);
}

private void CreateObjects()
{
// Configure the locations
livingRoom = new RoomWithDoor("living room", "an antique carpet", "an oak door with a brass knob");
kitchen = new RoomWithDoor("kitchen", "stainless steel appliances", "a screen door");
diningRoom = new Room("dining room", "a crystal chandelier");
frontYard = new OutsideWithDoor("front yard", false, livingRoom.DoorDescription);
backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
garden = new Outside("garden", false);

// Configure the exits
livingRoom.Exits = new Location { diningRoom };
kitchen.Exits = new Location { diningRoom };
diningRoom.Exits = new Location { livingRoom, kitchen };
frontYard.Exits = new Location { backYard, garden };
backYard.Exits = new Location { frontYard, garden };
garden.Exits = new Location { frontYard, backYard };

// Configure exterior doors
livingRoom.DoorLocation = frontYard;
frontYard.DoorLocation = livingRoom;
kitchen.DoorLocation = backYard;
backYard.DoorLocation = kitchen;
}

private void MoveToLocation(Location location)
{
currentLocation = location;
ExitsComboBox.Items.Clear();
foreach (Location exit in location.Exits)
{
ExitsComboBox.Items.Add(exit.Name);
}
ExitsComboBox.SelectedIndex = 0;
DescriptionTextBox.Text = currentLocation.Description;
ShowGoThroughExteriorDoorButton(currentLocation);
}

private void ShowGoThroughExteriorDoorButton(Location location)
{
if (location is IHasExteriorDoor)
{
GoThroughExteriorDoorButton.Visible = true;
return;
}
GoThroughExteriorDoorButton.Visible = false;
}

private void GoHereButton_Click(object sender, EventArgs e)
{
MoveToLocation(currentLocation.Exits[ExitsComboBox.SelectedIndex]);
}

private void GoThroughExteriorDoorButton_Click(object sender, EventArgs e)
{
IHasExteriorDoor locationWithExteriorDoor = currentLocation as IHasExteriorDoor;
MoveToLocation(locationWithExteriorDoor.DoorLocation);
}
}
}









share|improve this question











$endgroup$




I've been working through the book Head First C# and this is an exercise from chapter 7, which is about interfaces and abstract classes.



Note that the class design was mandated by the authors, I'm open to suggestions to improve.



I'd like a review on any and all aspects. My code ended up quite a bit different than the book's solution, because it's a few years old and I think "holds back" on newer C#/.NET features and syntax.



In a shared code base, I would include that XML documentation, but since this is just local code for learning, I didn't. Any and all feedback appreciated.





The authors' published implementation is here on GitHub. The idea is to implement a floor plan that can be explored, with room connections and doors to the outside. It's done through a WinForms application.



Here is a picture of the plan from the book. Sorry, couldn't find a screenshot.



floorplan



The app looks like this:



app





Location.cs



using System;

namespace House
{
abstract class Location
{
public Location(string name) => Name = name;

public string Name { get; private set; }
public Location Exits;

public virtual string Description
{
get
{
string description = $"You're standing in the {Name}. You see exits to the following places: rn";
foreach (Location exit in Exits)
{
description += $"— {exit.Name}";
if (exit != Exits[Exits.Length - 1])
{
description += "rn";
}
}
return description;
}
}
}
}


Room.cs



using System;

namespace House
{
class Room : Location
{
public Room(string name, string decoration)
: base(name)
{
Decoration = decoration;
}

private string Decoration { get; set; }

public override string Description => $"You see {Decoration}. {base.Description} ";
}
}


Outside.cs



using System;

namespace House
{
class Outside : Location
{
public Outside(string name, bool hot)
: base(name)
{
Hot = hot;
}

private bool Hot { get; }

override public string Description => Hot
? "It's very hot here. " + base.Description
: base.Description;
}
}




IHasInteriorDoor.cs



using System;

namespace House
{
interface IHasExteriorDoor
{
string DoorDescription { get; }
Location DoorLocation { get; }
}
}




OutsideWithDoor.cs



using System;

namespace House
{
class OutsideWithDoor : Outside, IHasExteriorDoor
{
public OutsideWithDoor(string name, bool hot, string doorDescription)
: base(name, hot)
{
DoorDescription = doorDescription;
}

public string DoorDescription { get; private set; }

public Location DoorLocation { get; set; }

public override string Description => $"{base.Description}rn You see {DoorDescription} to go inside.";
}
}


RoomWithDoor.cs



using System;

namespace House
{
class RoomWithDoor : Room, IHasExteriorDoor
{
public RoomWithDoor(string name, string decoration, string doorDescription)
: base(name, decoration)
{
DoorDescription = doorDescription;
}

public string DoorDescription { get; private set; }

public Location DoorLocation { get; set; }
}
}




And here is the WinForms to make it work. Leaving out IDE generated code.



ExploreTheHouseForm.cs



using System;
using System.Windows.Forms;

namespace House
{
public partial class ExploreTheHouseForm : Form
{
Location currentLocation;

RoomWithDoor livingRoom;
RoomWithDoor kitchen;
Room diningRoom;
OutsideWithDoor frontYard;
OutsideWithDoor backYard;
Outside garden;

public ExploreTheHouseForm()
{
InitializeComponent();
CreateObjects();
MoveToLocation(livingRoom);
}

private void CreateObjects()
{
// Configure the locations
livingRoom = new RoomWithDoor("living room", "an antique carpet", "an oak door with a brass knob");
kitchen = new RoomWithDoor("kitchen", "stainless steel appliances", "a screen door");
diningRoom = new Room("dining room", "a crystal chandelier");
frontYard = new OutsideWithDoor("front yard", false, livingRoom.DoorDescription);
backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
garden = new Outside("garden", false);

// Configure the exits
livingRoom.Exits = new Location { diningRoom };
kitchen.Exits = new Location { diningRoom };
diningRoom.Exits = new Location { livingRoom, kitchen };
frontYard.Exits = new Location { backYard, garden };
backYard.Exits = new Location { frontYard, garden };
garden.Exits = new Location { frontYard, backYard };

// Configure exterior doors
livingRoom.DoorLocation = frontYard;
frontYard.DoorLocation = livingRoom;
kitchen.DoorLocation = backYard;
backYard.DoorLocation = kitchen;
}

private void MoveToLocation(Location location)
{
currentLocation = location;
ExitsComboBox.Items.Clear();
foreach (Location exit in location.Exits)
{
ExitsComboBox.Items.Add(exit.Name);
}
ExitsComboBox.SelectedIndex = 0;
DescriptionTextBox.Text = currentLocation.Description;
ShowGoThroughExteriorDoorButton(currentLocation);
}

private void ShowGoThroughExteriorDoorButton(Location location)
{
if (location is IHasExteriorDoor)
{
GoThroughExteriorDoorButton.Visible = true;
return;
}
GoThroughExteriorDoorButton.Visible = false;
}

private void GoHereButton_Click(object sender, EventArgs e)
{
MoveToLocation(currentLocation.Exits[ExitsComboBox.SelectedIndex]);
}

private void GoThroughExteriorDoorButton_Click(object sender, EventArgs e)
{
IHasExteriorDoor locationWithExteriorDoor = currentLocation as IHasExteriorDoor;
MoveToLocation(locationWithExteriorDoor.DoorLocation);
}
}
}






c# winforms interface xaml






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited yesterday







Phrancis

















asked yesterday









PhrancisPhrancis

14.8k648141




14.8k648141












  • $begingroup$
    The winforms screen gives the option to go through the exterior door, but whatever is behind the exterior door doesn't get listed in the available places? What happens if the kitchen had multiple exterior doors to multiple places? What if the entire house was just a kitchen?
    $endgroup$
    – Mast
    yesterday






  • 3




    $begingroup$
    If you are interested in the general problem of how to write text-based adventures, I encourage you to look into Inform7, an exceedingly clever language for writing such adventures. If you're interested in learning about virtual machines for implementing such adventures, I did a series on my blog a few years ago about the Z-machine.
    $endgroup$
    – Eric Lippert
    17 hours ago






  • 1




    $begingroup$
    As a bonus, on a Z-machine code for this problem will occupy less than a kilobyte. You won't be able to compile the above code into a .NET assembly that takes up that much. (This is another way of saying "in Ye Olde Days we didn't have no interfaces and abstract classes". However, we were also much more likely to be eaten by a grue, so it all evens out in the end.)
    $endgroup$
    – Jeroen Mostert
    2 hours ago




















  • $begingroup$
    The winforms screen gives the option to go through the exterior door, but whatever is behind the exterior door doesn't get listed in the available places? What happens if the kitchen had multiple exterior doors to multiple places? What if the entire house was just a kitchen?
    $endgroup$
    – Mast
    yesterday






  • 3




    $begingroup$
    If you are interested in the general problem of how to write text-based adventures, I encourage you to look into Inform7, an exceedingly clever language for writing such adventures. If you're interested in learning about virtual machines for implementing such adventures, I did a series on my blog a few years ago about the Z-machine.
    $endgroup$
    – Eric Lippert
    17 hours ago






  • 1




    $begingroup$
    As a bonus, on a Z-machine code for this problem will occupy less than a kilobyte. You won't be able to compile the above code into a .NET assembly that takes up that much. (This is another way of saying "in Ye Olde Days we didn't have no interfaces and abstract classes". However, we were also much more likely to be eaten by a grue, so it all evens out in the end.)
    $endgroup$
    – Jeroen Mostert
    2 hours ago


















$begingroup$
The winforms screen gives the option to go through the exterior door, but whatever is behind the exterior door doesn't get listed in the available places? What happens if the kitchen had multiple exterior doors to multiple places? What if the entire house was just a kitchen?
$endgroup$
– Mast
yesterday




$begingroup$
The winforms screen gives the option to go through the exterior door, but whatever is behind the exterior door doesn't get listed in the available places? What happens if the kitchen had multiple exterior doors to multiple places? What if the entire house was just a kitchen?
$endgroup$
– Mast
yesterday




3




3




$begingroup$
If you are interested in the general problem of how to write text-based adventures, I encourage you to look into Inform7, an exceedingly clever language for writing such adventures. If you're interested in learning about virtual machines for implementing such adventures, I did a series on my blog a few years ago about the Z-machine.
$endgroup$
– Eric Lippert
17 hours ago




$begingroup$
If you are interested in the general problem of how to write text-based adventures, I encourage you to look into Inform7, an exceedingly clever language for writing such adventures. If you're interested in learning about virtual machines for implementing such adventures, I did a series on my blog a few years ago about the Z-machine.
$endgroup$
– Eric Lippert
17 hours ago




1




1




$begingroup$
As a bonus, on a Z-machine code for this problem will occupy less than a kilobyte. You won't be able to compile the above code into a .NET assembly that takes up that much. (This is another way of saying "in Ye Olde Days we didn't have no interfaces and abstract classes". However, we were also much more likely to be eaten by a grue, so it all evens out in the end.)
$endgroup$
– Jeroen Mostert
2 hours ago






$begingroup$
As a bonus, on a Z-machine code for this problem will occupy less than a kilobyte. You won't be able to compile the above code into a .NET assembly that takes up that much. (This is another way of saying "in Ye Olde Days we didn't have no interfaces and abstract classes". However, we were also much more likely to be eaten by a grue, so it all evens out in the end.)
$endgroup$
– Jeroen Mostert
2 hours ago












5 Answers
5






active

oldest

votes


















22












$begingroup$

I have a general rule: if I see string concatenation with a loop I assume it's not the best way of doing it. Let's look at this:



foreach (Location exit in Exits)
{
description += $"— {exit.Name}";
if (exit != Exits[Exits.Length - 1])
{
description += "rn";
}
}


I see this pattern a lot. What you need is Select with string.Join:



var exitList = string.Join(Environment.NewLine, Exits.Select(exit => $"— {exit.Name}"));


I've used Environment.NewLine because I like using well-named constants. Random aside: r is a Carriage Return and n is a Line Feed. The terminology comes from physical printers. Another reason I prefer Environment.NewLine is that it means you don't have to know and remember that.



Edit



A comment notes that Environment.NewLine is different on Windows vs Linux/Unix. I think that's important to know and I really should have mentioned it the first time. As the comment exchange shows, it's really easy to get platforms and line-endings mixed up which I think illustrates the usefulness of the constant.





The Name property of Location is only set in the constructor. You can use a read-only auto property instead of a private set one:



public string Name { get; }




Outside.Description could be using string interpolation like the other Description properties.





You're missing your access modifiers on your class declarations. MS guidelines state that they should always be specified.





I'm not aware of a convention for it but override public string seems to be an odd order to me. I would expect the access modifier first: public override string. The main thing is to keep that consistent though.






share|improve this answer











$endgroup$









  • 2




    $begingroup$
    Environment.NewLine is better for working with Windows or Linux because it contains a different value depending on the OS. For Windows, Environment.NewLine is "rn" and on Linux, it is "r", but by using Environment.NewLine you do not need to worry about those details.
    $endgroup$
    – Rick Davin
    yesterday






  • 1




    $begingroup$
    @RickDavin - yes, that's true. I think that r was old mac though, no? Unix/linux use n AFAIK.
    $endgroup$
    – RobH
    yesterday










  • $begingroup$
    My mistake. Yes Linux is "n". See stackoverflow.com/questions/1015766/…
    $endgroup$
    – Rick Davin
    23 hours ago



















8












$begingroup$

You've already made a decent effort to make your types immutable by setting its properties in a constructor and providing get-only access, not set - like you have with Location.Name for example. This is good practice whenever it is reasonable to do so (because, among other things, this means you can pass objects around without ever worrying that something else will change their state unexpectedly). However, I note that Location.Exits is a public field, which means it could be replaced with another array while the program is running - but the house is supposed to be fixed in structure. It would be better as a get-only public property, passed as another parameter in the constructor.



More subtly, not only could someone do something like currentLocation.Exits = ..., which the above advice would prevent; someone could also do currentLocation.Exits[0] = ..., and again they are changing something that is supposed to be fixed. (When I say "someone", understand that that could mean "you, by mistake", especially in a larger more complex program). Since you mentioned you have been learning about interfaces, the public get accessor for Location.Exits should be an IEnumerable<Location>, which lets things enumerate through the exits array to see what they are, but not change them. (If you've not used generics yet, don't worry too much about this for now).



So it would end up like this:



abstract class Location
{
public Location(string name, Location exits)
{
Name = name;
_exits = exits;
}

public string Name { get; }

private Location _exits;
public IEnumerable<Location> Exits => _exits;

// ...
}


(Secretly, I'm not super-happy about passing in exits as an array, either. Again, someone could change that array after creating the Location. I'd rather pass in exits as an IEnumerable<Location> and copy them into a new array or other container type private to the Location. But that raises some design and object ownership questions that aren't too relevant here so let's not worry about that).





Digging deeper on locations/exits - a couple of things here, which are probably too large a change to worry about for this exercise, but something to think about in future.



Firstly, OutsideWithDoor inherits from Outside and implements the IHasExteriorDoor interface. This works for your purposes, but means the question of whether or not an exit from one location to the next has a door depends on the type of the locations, whereas logically speaking it's a property of the connection between them. (It's also limited to only one door per location, and a few other tricky bits - prefer to avoid unnecessary inheritance, and prefer composition over inheritance). So, I would suggest a LocationConnection type, where Locations are joined by LocationConnections rather than directly to other Locations, and a LocationConnection can either have a door or not (a boolean property).



Secondly, Location exits are bi-directional, that is, if you can go from one location to another, you can always go back, too. That makes sense (if you go from the kitchen to the dining room, it'd be very odd to be unable to go back to the kitchen!) but depends on your initialization code always getting that right, which is a common source of bugs. (What if the building was a mansion, with a hundred rooms!?) This problem could go away if that LocationConnection type were well-implemented, someone could travel along it in either direction and it only needs to be coded once. Something to bear in mind in future: whenever you have to remember to write BA if you write AB, someone's going to forget to do it. Make their (your) lives easier!



Introducing that new type may be a bigger change than is really justified for this code review, but it could solve a couple of potential problems.





A couple of very minor comments on ShowGoThroughExteriorDoorButton. Firstly, the name of the method is OK, but it sounds like it's always going to show that button. I'd call it ShowOrHide..., though that's just my personal preference. Also, the if-statement in that method is a bit inelegant. I'd write simply:



GoThroughExteriorDoorButton.Visible = (location is IHasExteriorDoor);


...which gets rid of those naked true and false values, and also gets rid of that ugly return halfway through the method. In general, prefer methods to have a single point of exit, at the end, though this isn't always practical, especially when you start to use exceptions.





Always specify access modifiers, especially private for all those fields on the ExploreTheHouseForm. Also, a common convention for private fields is to prefix them with an underscore, e.g. private Location _currentLocation;, though this is not universally followed - I like it because it helps make obvious what's a parameter or local variable, and what's a member variable.






share|improve this answer










New contributor




BittermanAndy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






$endgroup$













  • $begingroup$
    The desire to have the Location immutable and take the exits in the constructor is great, but it seems incompatible with two-way travel. The kitchen has an exit to the dining room, and the dining room has an exit to to the kitchen. In order to follow your implementation advice, both would have to be created before the other. The proposed LocationConnection class seems to have the same chicken and egg problem preventing immutability. It will have to reference the locations it connects, and they will have to reference it. How would you resolve this conflict?
    $endgroup$
    – Mr.Mindor
    18 hours ago












  • $begingroup$
    Fair point. One possible solution would be to create the Locations without exit information, then create a list/LUT of LocationConnections. The Location then wouldn't itself hold references to its exits - every time it wanted to know, it would have to check the LocationConnection list. Is that better or worse than just allowing the Location class to be mutable? It becomes a judgement call at that point and depends on how the classes will be used. Mutable is great "whenever it is reasonable to do so"... it isn't always.
    $endgroup$
    – BittermanAndy
    17 hours ago








  • 1




    $begingroup$
    Very interesting answer, thank you! I'll be taking this into consideration in the future!
    $endgroup$
    – Phrancis
    13 hours ago










  • $begingroup$
    Comintern's (good) answer provides some examples of what the exit/connection types might look like.
    $endgroup$
    – BittermanAndy
    7 hours ago



















6












$begingroup$

The other reviews covered many of the main points I'd raise, but there are a handful of others.





Constructors in abstract classes should be protected, not public:




public Location(string name) => Name = name;    



You can't create a new instance of the abstract class, so it is for all intents and purposes protected anyway. Make the access modifier match the behavior.





I'm not sure that I like some of the naming. For example, CreateObjects() gives only the slightest clue as to what it is doing. I'd probably go with something more along the lines of GenerateMap(). A couple of the member names are also a little ambiguous as to how they function - for example, which room is IHasExteriorDoor.DoorLocation relative to?





Speaking of doors (keeping in mind that I'm not sure how much was proscribed by the exercise), I also think I find it a bit more natural to have the exits be the primary motive object. It's more natural to me to think of "using a door" than it is to think of "leaving a room". To me, a room is a static thing - it has exits, but you don't really use a room by leaving it. I'd consider building your map from the standpoint of the connections between the locations instead of the locations themselves. Something more like this:



public interface ILocation
{
string Name { get; }
string Description { get; }
}




public interface IExit
{
// TODO: my naming sucks too.
ILocation LocationOne { get; }
ILocation LocationTwo { get; }
// Takes the room you're exiting as a parameter, returns where you end up.
ILocation Traverse(ILocation from);
}




public abstract class Location : ILocation
{
private readonly IReadOnlyList<IExit> _exits;

protected Location(string name, string description, IEnumerable<IExit> exits)
{
_exits = exits.ToList();
}

public IEnumerable<IExit> Exits => _exits;

// ...other properties...
}


That lets you concentrate on the spatial relationships between the locations from a more natural direction (IMO and no pun intended). You'll likely find this to be more easily extensible down the road when you need to, say close or lock a door:



public interface Door : IExit
{
bool IsOpen { get; }
bool IsLocked { get; }
}





share|improve this answer









$endgroup$





















    5












    $begingroup$

    In this code block here:



    ...
    OutsideWithDoor backYard;
    Outside garden;

    public ExploreTheHouseForm()
    {
    InitializeComponent();
    CreateObjects(); // <--- bleh
    MoveToLocation(livingRoom);
    }


    This is call to the CreateObject() method, is something I don't like to see in code (it could be a personal style issue) but if you are constructing an object, then all code related to the construction of an object should just stay in the constructor...



    I would prefer that it ended up looking like



    ...
    private readonly OutsideWithDoor _backYard; // now it can be readonly
    private readonly Outside _garden;

    public ExploreTheHouseForm()
    {
    InitializeComponent();

    ...
    _backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
    _garden = new Outside("garden", false);

    MoveToLocation(livingRoom);
    }





    share|improve this answer









    $endgroup$





















      5












      $begingroup$

      RobH's review covers syntax and style well so I won't go into that. Instead I'd like to give my take on feedback given by Svek and BittermanAndy.



      Separation Of Concerns



      I think Svek's commentary about the CreateObjects method is spot on, but I don't think it goes far enough. The need for such a method in the first place hints that the ExploreTheHouseForm class is doing to much. With the current implementation, each of the rooms is a field on the form. This effectively makes the ExploreTheHouseForm the house itself. As such it would be more aptly named ExplorableHouseForm.



      In general (and this becomes ever more important as you get into more complex projects) we want to separate the presentation of the data from the data itself.



      The form is the UI it already has the duty to present the data to the user. It shouldn't also be the data. I would much rather the house be constructed elsewhere and passed to the form's constructor:



          public ExploreTheHouseForm(Location initialRoom)
      {
      InitializeComponent();
      MoveToLocation(initialRoom);
      }


      With this simple change, you can remove all the individual Location fields from ExploreTheHouseForm with the exception of currentLocation. Also, if you desire, you can use the same form to explore any number of different houses without further modification.



      Immutability



      A fair amount of BittermanAndy's advice (as of the time of this writing, his post was updated at least once since I started) was to try to make your Location class immutable. Since with the overall design as it is, locations need to reference each other, you run into a chicken & egg scenario preventing immutability where each Location needs their neighbors to be created before them. I don't see a way around this, however if you have your locations implement an interface, and write your form to consume the interface rather than Location you can get much of the same benefit of actual immutability.



      public interface ILocation
      {
      public string Name { get; }
      public IList<ILocation> Exits {get;}
      public string Description { get;}
      }


      In ILocation we only specify the get portions of the properties. This means to consumers of ILocation the properties are effectively read-only even if the implementing classes implement the set. We also declare Exits as a collection of ILocation rather than Location so that accessed members are also read-only to consumers.



      You don't have to change much about Location itself:



      public abstract class Location: ILocation
      {
      ...
      //private field to back Exits property.
      private IList<ILocation> _exits;

      public IList<ILocation> Exits {
      get
      {
      // AsReadOnly so that consumers are not allowed to modify contents.
      // there are other ways of accomplishing this that may be better overall, but ExploreTheHouseForm accesses Exits by index so we can only change it so much.
      return _exits?.AsReadOnly();
      }
      set{ _exits = value;}
      }
      }


      Updating ExploreTheHouseForm is also straight forward, simply change the type of the field currentLocation the Location parameters of ExploreTheHouseForm, MoveToLocation, and ShowGoThroughExteriorDoorButton to ILocation:



          ...
      private ILocation _currentLocation;
      ...
      public ExploreTheHouseForm(ILocation initialRoom)
      {
      InitializeComponent();
      MoveToLocation(initialRoom);
      }
      ...
      private void MoveToLocation(ILocation location)
      ...
      private void ShowGoThroughExteriorDoorButton(ILocation location)


      The overall impact of this is that the locations are mutable during construction (by some factory) but once construction is complete, all you work with the read-only ILocation



      // GenerateHouse returns the entry point of the house.

      public ILocation GenerateHouse()
      {
      // Configure the locations
      var livingRoom = new RoomWithDoor("living room", "an antique carpet", "an oak door with a brass knob");
      var kitchen = new RoomWithDoor("kitchen", "stainless steel appliances", "a screen door");
      var diningRoom = new Room("dining room", "a crystal chandelier");
      var frontYard = new OutsideWithDoor("front yard", false, livingRoom.DoorDescription);
      var backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
      var garden = new Outside("garden", false);

      // Configure the exits
      livingRoom.Exits = new List<ILocation>() { diningRoom };
      kitchen.Exits = new List<ILocation>() { diningRoom };
      diningRoom.Exits = new List<ILocation> { livingRoom, kitchen };
      frontYard.Exits = new List<ILocation> { backYard, garden };
      backYard.Exits = new List<ILocation> { frontYard, garden };
      garden.Exits = new List<ILocation> { frontYard, backYard };

      // Configure exterior doors
      livingRoom.DoorLocation = frontYard;
      frontYard.DoorLocation = livingRoom;
      kitchen.DoorLocation = backYard;
      backYard.DoorLocation = kitchen;
      // return entry point.
      return livingRoom;

      }


      Location Connectivity



      I agree with the other reviews and comments that pulling the concept of the location connection into a separate class/set of classes would allow for a better design. Locations can have any number of exits, and it is a property of the exit, not the location if that exit is an open archway, a door, or just an abstract dividing line (outdoor location to outdoor location) Comintern does a good job of covering this in their review so I won't go into it further.






      share|improve this answer









      $endgroup$









      • 1




        $begingroup$
        Excellent answer. Separating the creation of the house, and presenting the form only with the interface it needs for navigation, solves the creation / immutability problem very nicely. (My edits were fairly minor, by the way).
        $endgroup$
        – BittermanAndy
        7 hours ago











      Your Answer





      StackExchange.ifUsing("editor", function () {
      return StackExchange.using("mathjaxEditing", function () {
      StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
      StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
      });
      });
      }, "mathjax-editing");

      StackExchange.ifUsing("editor", function () {
      StackExchange.using("externalEditor", function () {
      StackExchange.using("snippets", function () {
      StackExchange.snippets.init();
      });
      });
      }, "code-snippets");

      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "196"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: false,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211662%2fhouse-plan-design-head-first-c%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      5 Answers
      5






      active

      oldest

      votes








      5 Answers
      5






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      22












      $begingroup$

      I have a general rule: if I see string concatenation with a loop I assume it's not the best way of doing it. Let's look at this:



      foreach (Location exit in Exits)
      {
      description += $"— {exit.Name}";
      if (exit != Exits[Exits.Length - 1])
      {
      description += "rn";
      }
      }


      I see this pattern a lot. What you need is Select with string.Join:



      var exitList = string.Join(Environment.NewLine, Exits.Select(exit => $"— {exit.Name}"));


      I've used Environment.NewLine because I like using well-named constants. Random aside: r is a Carriage Return and n is a Line Feed. The terminology comes from physical printers. Another reason I prefer Environment.NewLine is that it means you don't have to know and remember that.



      Edit



      A comment notes that Environment.NewLine is different on Windows vs Linux/Unix. I think that's important to know and I really should have mentioned it the first time. As the comment exchange shows, it's really easy to get platforms and line-endings mixed up which I think illustrates the usefulness of the constant.





      The Name property of Location is only set in the constructor. You can use a read-only auto property instead of a private set one:



      public string Name { get; }




      Outside.Description could be using string interpolation like the other Description properties.





      You're missing your access modifiers on your class declarations. MS guidelines state that they should always be specified.





      I'm not aware of a convention for it but override public string seems to be an odd order to me. I would expect the access modifier first: public override string. The main thing is to keep that consistent though.






      share|improve this answer











      $endgroup$









      • 2




        $begingroup$
        Environment.NewLine is better for working with Windows or Linux because it contains a different value depending on the OS. For Windows, Environment.NewLine is "rn" and on Linux, it is "r", but by using Environment.NewLine you do not need to worry about those details.
        $endgroup$
        – Rick Davin
        yesterday






      • 1




        $begingroup$
        @RickDavin - yes, that's true. I think that r was old mac though, no? Unix/linux use n AFAIK.
        $endgroup$
        – RobH
        yesterday










      • $begingroup$
        My mistake. Yes Linux is "n". See stackoverflow.com/questions/1015766/…
        $endgroup$
        – Rick Davin
        23 hours ago
















      22












      $begingroup$

      I have a general rule: if I see string concatenation with a loop I assume it's not the best way of doing it. Let's look at this:



      foreach (Location exit in Exits)
      {
      description += $"— {exit.Name}";
      if (exit != Exits[Exits.Length - 1])
      {
      description += "rn";
      }
      }


      I see this pattern a lot. What you need is Select with string.Join:



      var exitList = string.Join(Environment.NewLine, Exits.Select(exit => $"— {exit.Name}"));


      I've used Environment.NewLine because I like using well-named constants. Random aside: r is a Carriage Return and n is a Line Feed. The terminology comes from physical printers. Another reason I prefer Environment.NewLine is that it means you don't have to know and remember that.



      Edit



      A comment notes that Environment.NewLine is different on Windows vs Linux/Unix. I think that's important to know and I really should have mentioned it the first time. As the comment exchange shows, it's really easy to get platforms and line-endings mixed up which I think illustrates the usefulness of the constant.





      The Name property of Location is only set in the constructor. You can use a read-only auto property instead of a private set one:



      public string Name { get; }




      Outside.Description could be using string interpolation like the other Description properties.





      You're missing your access modifiers on your class declarations. MS guidelines state that they should always be specified.





      I'm not aware of a convention for it but override public string seems to be an odd order to me. I would expect the access modifier first: public override string. The main thing is to keep that consistent though.






      share|improve this answer











      $endgroup$









      • 2




        $begingroup$
        Environment.NewLine is better for working with Windows or Linux because it contains a different value depending on the OS. For Windows, Environment.NewLine is "rn" and on Linux, it is "r", but by using Environment.NewLine you do not need to worry about those details.
        $endgroup$
        – Rick Davin
        yesterday






      • 1




        $begingroup$
        @RickDavin - yes, that's true. I think that r was old mac though, no? Unix/linux use n AFAIK.
        $endgroup$
        – RobH
        yesterday










      • $begingroup$
        My mistake. Yes Linux is "n". See stackoverflow.com/questions/1015766/…
        $endgroup$
        – Rick Davin
        23 hours ago














      22












      22








      22





      $begingroup$

      I have a general rule: if I see string concatenation with a loop I assume it's not the best way of doing it. Let's look at this:



      foreach (Location exit in Exits)
      {
      description += $"— {exit.Name}";
      if (exit != Exits[Exits.Length - 1])
      {
      description += "rn";
      }
      }


      I see this pattern a lot. What you need is Select with string.Join:



      var exitList = string.Join(Environment.NewLine, Exits.Select(exit => $"— {exit.Name}"));


      I've used Environment.NewLine because I like using well-named constants. Random aside: r is a Carriage Return and n is a Line Feed. The terminology comes from physical printers. Another reason I prefer Environment.NewLine is that it means you don't have to know and remember that.



      Edit



      A comment notes that Environment.NewLine is different on Windows vs Linux/Unix. I think that's important to know and I really should have mentioned it the first time. As the comment exchange shows, it's really easy to get platforms and line-endings mixed up which I think illustrates the usefulness of the constant.





      The Name property of Location is only set in the constructor. You can use a read-only auto property instead of a private set one:



      public string Name { get; }




      Outside.Description could be using string interpolation like the other Description properties.





      You're missing your access modifiers on your class declarations. MS guidelines state that they should always be specified.





      I'm not aware of a convention for it but override public string seems to be an odd order to me. I would expect the access modifier first: public override string. The main thing is to keep that consistent though.






      share|improve this answer











      $endgroup$



      I have a general rule: if I see string concatenation with a loop I assume it's not the best way of doing it. Let's look at this:



      foreach (Location exit in Exits)
      {
      description += $"— {exit.Name}";
      if (exit != Exits[Exits.Length - 1])
      {
      description += "rn";
      }
      }


      I see this pattern a lot. What you need is Select with string.Join:



      var exitList = string.Join(Environment.NewLine, Exits.Select(exit => $"— {exit.Name}"));


      I've used Environment.NewLine because I like using well-named constants. Random aside: r is a Carriage Return and n is a Line Feed. The terminology comes from physical printers. Another reason I prefer Environment.NewLine is that it means you don't have to know and remember that.



      Edit



      A comment notes that Environment.NewLine is different on Windows vs Linux/Unix. I think that's important to know and I really should have mentioned it the first time. As the comment exchange shows, it's really easy to get platforms and line-endings mixed up which I think illustrates the usefulness of the constant.





      The Name property of Location is only set in the constructor. You can use a read-only auto property instead of a private set one:



      public string Name { get; }




      Outside.Description could be using string interpolation like the other Description properties.





      You're missing your access modifiers on your class declarations. MS guidelines state that they should always be specified.





      I'm not aware of a convention for it but override public string seems to be an odd order to me. I would expect the access modifier first: public override string. The main thing is to keep that consistent though.







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited 7 hours ago

























      answered yesterday









      RobHRobH

      14.6k42662




      14.6k42662








      • 2




        $begingroup$
        Environment.NewLine is better for working with Windows or Linux because it contains a different value depending on the OS. For Windows, Environment.NewLine is "rn" and on Linux, it is "r", but by using Environment.NewLine you do not need to worry about those details.
        $endgroup$
        – Rick Davin
        yesterday






      • 1




        $begingroup$
        @RickDavin - yes, that's true. I think that r was old mac though, no? Unix/linux use n AFAIK.
        $endgroup$
        – RobH
        yesterday










      • $begingroup$
        My mistake. Yes Linux is "n". See stackoverflow.com/questions/1015766/…
        $endgroup$
        – Rick Davin
        23 hours ago














      • 2




        $begingroup$
        Environment.NewLine is better for working with Windows or Linux because it contains a different value depending on the OS. For Windows, Environment.NewLine is "rn" and on Linux, it is "r", but by using Environment.NewLine you do not need to worry about those details.
        $endgroup$
        – Rick Davin
        yesterday






      • 1




        $begingroup$
        @RickDavin - yes, that's true. I think that r was old mac though, no? Unix/linux use n AFAIK.
        $endgroup$
        – RobH
        yesterday










      • $begingroup$
        My mistake. Yes Linux is "n". See stackoverflow.com/questions/1015766/…
        $endgroup$
        – Rick Davin
        23 hours ago








      2




      2




      $begingroup$
      Environment.NewLine is better for working with Windows or Linux because it contains a different value depending on the OS. For Windows, Environment.NewLine is "rn" and on Linux, it is "r", but by using Environment.NewLine you do not need to worry about those details.
      $endgroup$
      – Rick Davin
      yesterday




      $begingroup$
      Environment.NewLine is better for working with Windows or Linux because it contains a different value depending on the OS. For Windows, Environment.NewLine is "rn" and on Linux, it is "r", but by using Environment.NewLine you do not need to worry about those details.
      $endgroup$
      – Rick Davin
      yesterday




      1




      1




      $begingroup$
      @RickDavin - yes, that's true. I think that r was old mac though, no? Unix/linux use n AFAIK.
      $endgroup$
      – RobH
      yesterday




      $begingroup$
      @RickDavin - yes, that's true. I think that r was old mac though, no? Unix/linux use n AFAIK.
      $endgroup$
      – RobH
      yesterday












      $begingroup$
      My mistake. Yes Linux is "n". See stackoverflow.com/questions/1015766/…
      $endgroup$
      – Rick Davin
      23 hours ago




      $begingroup$
      My mistake. Yes Linux is "n". See stackoverflow.com/questions/1015766/…
      $endgroup$
      – Rick Davin
      23 hours ago













      8












      $begingroup$

      You've already made a decent effort to make your types immutable by setting its properties in a constructor and providing get-only access, not set - like you have with Location.Name for example. This is good practice whenever it is reasonable to do so (because, among other things, this means you can pass objects around without ever worrying that something else will change their state unexpectedly). However, I note that Location.Exits is a public field, which means it could be replaced with another array while the program is running - but the house is supposed to be fixed in structure. It would be better as a get-only public property, passed as another parameter in the constructor.



      More subtly, not only could someone do something like currentLocation.Exits = ..., which the above advice would prevent; someone could also do currentLocation.Exits[0] = ..., and again they are changing something that is supposed to be fixed. (When I say "someone", understand that that could mean "you, by mistake", especially in a larger more complex program). Since you mentioned you have been learning about interfaces, the public get accessor for Location.Exits should be an IEnumerable<Location>, which lets things enumerate through the exits array to see what they are, but not change them. (If you've not used generics yet, don't worry too much about this for now).



      So it would end up like this:



      abstract class Location
      {
      public Location(string name, Location exits)
      {
      Name = name;
      _exits = exits;
      }

      public string Name { get; }

      private Location _exits;
      public IEnumerable<Location> Exits => _exits;

      // ...
      }


      (Secretly, I'm not super-happy about passing in exits as an array, either. Again, someone could change that array after creating the Location. I'd rather pass in exits as an IEnumerable<Location> and copy them into a new array or other container type private to the Location. But that raises some design and object ownership questions that aren't too relevant here so let's not worry about that).





      Digging deeper on locations/exits - a couple of things here, which are probably too large a change to worry about for this exercise, but something to think about in future.



      Firstly, OutsideWithDoor inherits from Outside and implements the IHasExteriorDoor interface. This works for your purposes, but means the question of whether or not an exit from one location to the next has a door depends on the type of the locations, whereas logically speaking it's a property of the connection between them. (It's also limited to only one door per location, and a few other tricky bits - prefer to avoid unnecessary inheritance, and prefer composition over inheritance). So, I would suggest a LocationConnection type, where Locations are joined by LocationConnections rather than directly to other Locations, and a LocationConnection can either have a door or not (a boolean property).



      Secondly, Location exits are bi-directional, that is, if you can go from one location to another, you can always go back, too. That makes sense (if you go from the kitchen to the dining room, it'd be very odd to be unable to go back to the kitchen!) but depends on your initialization code always getting that right, which is a common source of bugs. (What if the building was a mansion, with a hundred rooms!?) This problem could go away if that LocationConnection type were well-implemented, someone could travel along it in either direction and it only needs to be coded once. Something to bear in mind in future: whenever you have to remember to write BA if you write AB, someone's going to forget to do it. Make their (your) lives easier!



      Introducing that new type may be a bigger change than is really justified for this code review, but it could solve a couple of potential problems.





      A couple of very minor comments on ShowGoThroughExteriorDoorButton. Firstly, the name of the method is OK, but it sounds like it's always going to show that button. I'd call it ShowOrHide..., though that's just my personal preference. Also, the if-statement in that method is a bit inelegant. I'd write simply:



      GoThroughExteriorDoorButton.Visible = (location is IHasExteriorDoor);


      ...which gets rid of those naked true and false values, and also gets rid of that ugly return halfway through the method. In general, prefer methods to have a single point of exit, at the end, though this isn't always practical, especially when you start to use exceptions.





      Always specify access modifiers, especially private for all those fields on the ExploreTheHouseForm. Also, a common convention for private fields is to prefix them with an underscore, e.g. private Location _currentLocation;, though this is not universally followed - I like it because it helps make obvious what's a parameter or local variable, and what's a member variable.






      share|improve this answer










      New contributor




      BittermanAndy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      $endgroup$













      • $begingroup$
        The desire to have the Location immutable and take the exits in the constructor is great, but it seems incompatible with two-way travel. The kitchen has an exit to the dining room, and the dining room has an exit to to the kitchen. In order to follow your implementation advice, both would have to be created before the other. The proposed LocationConnection class seems to have the same chicken and egg problem preventing immutability. It will have to reference the locations it connects, and they will have to reference it. How would you resolve this conflict?
        $endgroup$
        – Mr.Mindor
        18 hours ago












      • $begingroup$
        Fair point. One possible solution would be to create the Locations without exit information, then create a list/LUT of LocationConnections. The Location then wouldn't itself hold references to its exits - every time it wanted to know, it would have to check the LocationConnection list. Is that better or worse than just allowing the Location class to be mutable? It becomes a judgement call at that point and depends on how the classes will be used. Mutable is great "whenever it is reasonable to do so"... it isn't always.
        $endgroup$
        – BittermanAndy
        17 hours ago








      • 1




        $begingroup$
        Very interesting answer, thank you! I'll be taking this into consideration in the future!
        $endgroup$
        – Phrancis
        13 hours ago










      • $begingroup$
        Comintern's (good) answer provides some examples of what the exit/connection types might look like.
        $endgroup$
        – BittermanAndy
        7 hours ago
















      8












      $begingroup$

      You've already made a decent effort to make your types immutable by setting its properties in a constructor and providing get-only access, not set - like you have with Location.Name for example. This is good practice whenever it is reasonable to do so (because, among other things, this means you can pass objects around without ever worrying that something else will change their state unexpectedly). However, I note that Location.Exits is a public field, which means it could be replaced with another array while the program is running - but the house is supposed to be fixed in structure. It would be better as a get-only public property, passed as another parameter in the constructor.



      More subtly, not only could someone do something like currentLocation.Exits = ..., which the above advice would prevent; someone could also do currentLocation.Exits[0] = ..., and again they are changing something that is supposed to be fixed. (When I say "someone", understand that that could mean "you, by mistake", especially in a larger more complex program). Since you mentioned you have been learning about interfaces, the public get accessor for Location.Exits should be an IEnumerable<Location>, which lets things enumerate through the exits array to see what they are, but not change them. (If you've not used generics yet, don't worry too much about this for now).



      So it would end up like this:



      abstract class Location
      {
      public Location(string name, Location exits)
      {
      Name = name;
      _exits = exits;
      }

      public string Name { get; }

      private Location _exits;
      public IEnumerable<Location> Exits => _exits;

      // ...
      }


      (Secretly, I'm not super-happy about passing in exits as an array, either. Again, someone could change that array after creating the Location. I'd rather pass in exits as an IEnumerable<Location> and copy them into a new array or other container type private to the Location. But that raises some design and object ownership questions that aren't too relevant here so let's not worry about that).





      Digging deeper on locations/exits - a couple of things here, which are probably too large a change to worry about for this exercise, but something to think about in future.



      Firstly, OutsideWithDoor inherits from Outside and implements the IHasExteriorDoor interface. This works for your purposes, but means the question of whether or not an exit from one location to the next has a door depends on the type of the locations, whereas logically speaking it's a property of the connection between them. (It's also limited to only one door per location, and a few other tricky bits - prefer to avoid unnecessary inheritance, and prefer composition over inheritance). So, I would suggest a LocationConnection type, where Locations are joined by LocationConnections rather than directly to other Locations, and a LocationConnection can either have a door or not (a boolean property).



      Secondly, Location exits are bi-directional, that is, if you can go from one location to another, you can always go back, too. That makes sense (if you go from the kitchen to the dining room, it'd be very odd to be unable to go back to the kitchen!) but depends on your initialization code always getting that right, which is a common source of bugs. (What if the building was a mansion, with a hundred rooms!?) This problem could go away if that LocationConnection type were well-implemented, someone could travel along it in either direction and it only needs to be coded once. Something to bear in mind in future: whenever you have to remember to write BA if you write AB, someone's going to forget to do it. Make their (your) lives easier!



      Introducing that new type may be a bigger change than is really justified for this code review, but it could solve a couple of potential problems.





      A couple of very minor comments on ShowGoThroughExteriorDoorButton. Firstly, the name of the method is OK, but it sounds like it's always going to show that button. I'd call it ShowOrHide..., though that's just my personal preference. Also, the if-statement in that method is a bit inelegant. I'd write simply:



      GoThroughExteriorDoorButton.Visible = (location is IHasExteriorDoor);


      ...which gets rid of those naked true and false values, and also gets rid of that ugly return halfway through the method. In general, prefer methods to have a single point of exit, at the end, though this isn't always practical, especially when you start to use exceptions.





      Always specify access modifiers, especially private for all those fields on the ExploreTheHouseForm. Also, a common convention for private fields is to prefix them with an underscore, e.g. private Location _currentLocation;, though this is not universally followed - I like it because it helps make obvious what's a parameter or local variable, and what's a member variable.






      share|improve this answer










      New contributor




      BittermanAndy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      $endgroup$













      • $begingroup$
        The desire to have the Location immutable and take the exits in the constructor is great, but it seems incompatible with two-way travel. The kitchen has an exit to the dining room, and the dining room has an exit to to the kitchen. In order to follow your implementation advice, both would have to be created before the other. The proposed LocationConnection class seems to have the same chicken and egg problem preventing immutability. It will have to reference the locations it connects, and they will have to reference it. How would you resolve this conflict?
        $endgroup$
        – Mr.Mindor
        18 hours ago












      • $begingroup$
        Fair point. One possible solution would be to create the Locations without exit information, then create a list/LUT of LocationConnections. The Location then wouldn't itself hold references to its exits - every time it wanted to know, it would have to check the LocationConnection list. Is that better or worse than just allowing the Location class to be mutable? It becomes a judgement call at that point and depends on how the classes will be used. Mutable is great "whenever it is reasonable to do so"... it isn't always.
        $endgroup$
        – BittermanAndy
        17 hours ago








      • 1




        $begingroup$
        Very interesting answer, thank you! I'll be taking this into consideration in the future!
        $endgroup$
        – Phrancis
        13 hours ago










      • $begingroup$
        Comintern's (good) answer provides some examples of what the exit/connection types might look like.
        $endgroup$
        – BittermanAndy
        7 hours ago














      8












      8








      8





      $begingroup$

      You've already made a decent effort to make your types immutable by setting its properties in a constructor and providing get-only access, not set - like you have with Location.Name for example. This is good practice whenever it is reasonable to do so (because, among other things, this means you can pass objects around without ever worrying that something else will change their state unexpectedly). However, I note that Location.Exits is a public field, which means it could be replaced with another array while the program is running - but the house is supposed to be fixed in structure. It would be better as a get-only public property, passed as another parameter in the constructor.



      More subtly, not only could someone do something like currentLocation.Exits = ..., which the above advice would prevent; someone could also do currentLocation.Exits[0] = ..., and again they are changing something that is supposed to be fixed. (When I say "someone", understand that that could mean "you, by mistake", especially in a larger more complex program). Since you mentioned you have been learning about interfaces, the public get accessor for Location.Exits should be an IEnumerable<Location>, which lets things enumerate through the exits array to see what they are, but not change them. (If you've not used generics yet, don't worry too much about this for now).



      So it would end up like this:



      abstract class Location
      {
      public Location(string name, Location exits)
      {
      Name = name;
      _exits = exits;
      }

      public string Name { get; }

      private Location _exits;
      public IEnumerable<Location> Exits => _exits;

      // ...
      }


      (Secretly, I'm not super-happy about passing in exits as an array, either. Again, someone could change that array after creating the Location. I'd rather pass in exits as an IEnumerable<Location> and copy them into a new array or other container type private to the Location. But that raises some design and object ownership questions that aren't too relevant here so let's not worry about that).





      Digging deeper on locations/exits - a couple of things here, which are probably too large a change to worry about for this exercise, but something to think about in future.



      Firstly, OutsideWithDoor inherits from Outside and implements the IHasExteriorDoor interface. This works for your purposes, but means the question of whether or not an exit from one location to the next has a door depends on the type of the locations, whereas logically speaking it's a property of the connection between them. (It's also limited to only one door per location, and a few other tricky bits - prefer to avoid unnecessary inheritance, and prefer composition over inheritance). So, I would suggest a LocationConnection type, where Locations are joined by LocationConnections rather than directly to other Locations, and a LocationConnection can either have a door or not (a boolean property).



      Secondly, Location exits are bi-directional, that is, if you can go from one location to another, you can always go back, too. That makes sense (if you go from the kitchen to the dining room, it'd be very odd to be unable to go back to the kitchen!) but depends on your initialization code always getting that right, which is a common source of bugs. (What if the building was a mansion, with a hundred rooms!?) This problem could go away if that LocationConnection type were well-implemented, someone could travel along it in either direction and it only needs to be coded once. Something to bear in mind in future: whenever you have to remember to write BA if you write AB, someone's going to forget to do it. Make their (your) lives easier!



      Introducing that new type may be a bigger change than is really justified for this code review, but it could solve a couple of potential problems.





      A couple of very minor comments on ShowGoThroughExteriorDoorButton. Firstly, the name of the method is OK, but it sounds like it's always going to show that button. I'd call it ShowOrHide..., though that's just my personal preference. Also, the if-statement in that method is a bit inelegant. I'd write simply:



      GoThroughExteriorDoorButton.Visible = (location is IHasExteriorDoor);


      ...which gets rid of those naked true and false values, and also gets rid of that ugly return halfway through the method. In general, prefer methods to have a single point of exit, at the end, though this isn't always practical, especially when you start to use exceptions.





      Always specify access modifiers, especially private for all those fields on the ExploreTheHouseForm. Also, a common convention for private fields is to prefix them with an underscore, e.g. private Location _currentLocation;, though this is not universally followed - I like it because it helps make obvious what's a parameter or local variable, and what's a member variable.






      share|improve this answer










      New contributor




      BittermanAndy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      $endgroup$



      You've already made a decent effort to make your types immutable by setting its properties in a constructor and providing get-only access, not set - like you have with Location.Name for example. This is good practice whenever it is reasonable to do so (because, among other things, this means you can pass objects around without ever worrying that something else will change their state unexpectedly). However, I note that Location.Exits is a public field, which means it could be replaced with another array while the program is running - but the house is supposed to be fixed in structure. It would be better as a get-only public property, passed as another parameter in the constructor.



      More subtly, not only could someone do something like currentLocation.Exits = ..., which the above advice would prevent; someone could also do currentLocation.Exits[0] = ..., and again they are changing something that is supposed to be fixed. (When I say "someone", understand that that could mean "you, by mistake", especially in a larger more complex program). Since you mentioned you have been learning about interfaces, the public get accessor for Location.Exits should be an IEnumerable<Location>, which lets things enumerate through the exits array to see what they are, but not change them. (If you've not used generics yet, don't worry too much about this for now).



      So it would end up like this:



      abstract class Location
      {
      public Location(string name, Location exits)
      {
      Name = name;
      _exits = exits;
      }

      public string Name { get; }

      private Location _exits;
      public IEnumerable<Location> Exits => _exits;

      // ...
      }


      (Secretly, I'm not super-happy about passing in exits as an array, either. Again, someone could change that array after creating the Location. I'd rather pass in exits as an IEnumerable<Location> and copy them into a new array or other container type private to the Location. But that raises some design and object ownership questions that aren't too relevant here so let's not worry about that).





      Digging deeper on locations/exits - a couple of things here, which are probably too large a change to worry about for this exercise, but something to think about in future.



      Firstly, OutsideWithDoor inherits from Outside and implements the IHasExteriorDoor interface. This works for your purposes, but means the question of whether or not an exit from one location to the next has a door depends on the type of the locations, whereas logically speaking it's a property of the connection between them. (It's also limited to only one door per location, and a few other tricky bits - prefer to avoid unnecessary inheritance, and prefer composition over inheritance). So, I would suggest a LocationConnection type, where Locations are joined by LocationConnections rather than directly to other Locations, and a LocationConnection can either have a door or not (a boolean property).



      Secondly, Location exits are bi-directional, that is, if you can go from one location to another, you can always go back, too. That makes sense (if you go from the kitchen to the dining room, it'd be very odd to be unable to go back to the kitchen!) but depends on your initialization code always getting that right, which is a common source of bugs. (What if the building was a mansion, with a hundred rooms!?) This problem could go away if that LocationConnection type were well-implemented, someone could travel along it in either direction and it only needs to be coded once. Something to bear in mind in future: whenever you have to remember to write BA if you write AB, someone's going to forget to do it. Make their (your) lives easier!



      Introducing that new type may be a bigger change than is really justified for this code review, but it could solve a couple of potential problems.





      A couple of very minor comments on ShowGoThroughExteriorDoorButton. Firstly, the name of the method is OK, but it sounds like it's always going to show that button. I'd call it ShowOrHide..., though that's just my personal preference. Also, the if-statement in that method is a bit inelegant. I'd write simply:



      GoThroughExteriorDoorButton.Visible = (location is IHasExteriorDoor);


      ...which gets rid of those naked true and false values, and also gets rid of that ugly return halfway through the method. In general, prefer methods to have a single point of exit, at the end, though this isn't always practical, especially when you start to use exceptions.





      Always specify access modifiers, especially private for all those fields on the ExploreTheHouseForm. Also, a common convention for private fields is to prefix them with an underscore, e.g. private Location _currentLocation;, though this is not universally followed - I like it because it helps make obvious what's a parameter or local variable, and what's a member variable.







      share|improve this answer










      New contributor




      BittermanAndy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      share|improve this answer



      share|improve this answer








      edited 17 hours ago





















      New contributor




      BittermanAndy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      answered 21 hours ago









      BittermanAndyBittermanAndy

      1813




      1813




      New contributor




      BittermanAndy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      BittermanAndy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      BittermanAndy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.












      • $begingroup$
        The desire to have the Location immutable and take the exits in the constructor is great, but it seems incompatible with two-way travel. The kitchen has an exit to the dining room, and the dining room has an exit to to the kitchen. In order to follow your implementation advice, both would have to be created before the other. The proposed LocationConnection class seems to have the same chicken and egg problem preventing immutability. It will have to reference the locations it connects, and they will have to reference it. How would you resolve this conflict?
        $endgroup$
        – Mr.Mindor
        18 hours ago












      • $begingroup$
        Fair point. One possible solution would be to create the Locations without exit information, then create a list/LUT of LocationConnections. The Location then wouldn't itself hold references to its exits - every time it wanted to know, it would have to check the LocationConnection list. Is that better or worse than just allowing the Location class to be mutable? It becomes a judgement call at that point and depends on how the classes will be used. Mutable is great "whenever it is reasonable to do so"... it isn't always.
        $endgroup$
        – BittermanAndy
        17 hours ago








      • 1




        $begingroup$
        Very interesting answer, thank you! I'll be taking this into consideration in the future!
        $endgroup$
        – Phrancis
        13 hours ago










      • $begingroup$
        Comintern's (good) answer provides some examples of what the exit/connection types might look like.
        $endgroup$
        – BittermanAndy
        7 hours ago


















      • $begingroup$
        The desire to have the Location immutable and take the exits in the constructor is great, but it seems incompatible with two-way travel. The kitchen has an exit to the dining room, and the dining room has an exit to to the kitchen. In order to follow your implementation advice, both would have to be created before the other. The proposed LocationConnection class seems to have the same chicken and egg problem preventing immutability. It will have to reference the locations it connects, and they will have to reference it. How would you resolve this conflict?
        $endgroup$
        – Mr.Mindor
        18 hours ago












      • $begingroup$
        Fair point. One possible solution would be to create the Locations without exit information, then create a list/LUT of LocationConnections. The Location then wouldn't itself hold references to its exits - every time it wanted to know, it would have to check the LocationConnection list. Is that better or worse than just allowing the Location class to be mutable? It becomes a judgement call at that point and depends on how the classes will be used. Mutable is great "whenever it is reasonable to do so"... it isn't always.
        $endgroup$
        – BittermanAndy
        17 hours ago








      • 1




        $begingroup$
        Very interesting answer, thank you! I'll be taking this into consideration in the future!
        $endgroup$
        – Phrancis
        13 hours ago










      • $begingroup$
        Comintern's (good) answer provides some examples of what the exit/connection types might look like.
        $endgroup$
        – BittermanAndy
        7 hours ago
















      $begingroup$
      The desire to have the Location immutable and take the exits in the constructor is great, but it seems incompatible with two-way travel. The kitchen has an exit to the dining room, and the dining room has an exit to to the kitchen. In order to follow your implementation advice, both would have to be created before the other. The proposed LocationConnection class seems to have the same chicken and egg problem preventing immutability. It will have to reference the locations it connects, and they will have to reference it. How would you resolve this conflict?
      $endgroup$
      – Mr.Mindor
      18 hours ago






      $begingroup$
      The desire to have the Location immutable and take the exits in the constructor is great, but it seems incompatible with two-way travel. The kitchen has an exit to the dining room, and the dining room has an exit to to the kitchen. In order to follow your implementation advice, both would have to be created before the other. The proposed LocationConnection class seems to have the same chicken and egg problem preventing immutability. It will have to reference the locations it connects, and they will have to reference it. How would you resolve this conflict?
      $endgroup$
      – Mr.Mindor
      18 hours ago














      $begingroup$
      Fair point. One possible solution would be to create the Locations without exit information, then create a list/LUT of LocationConnections. The Location then wouldn't itself hold references to its exits - every time it wanted to know, it would have to check the LocationConnection list. Is that better or worse than just allowing the Location class to be mutable? It becomes a judgement call at that point and depends on how the classes will be used. Mutable is great "whenever it is reasonable to do so"... it isn't always.
      $endgroup$
      – BittermanAndy
      17 hours ago






      $begingroup$
      Fair point. One possible solution would be to create the Locations without exit information, then create a list/LUT of LocationConnections. The Location then wouldn't itself hold references to its exits - every time it wanted to know, it would have to check the LocationConnection list. Is that better or worse than just allowing the Location class to be mutable? It becomes a judgement call at that point and depends on how the classes will be used. Mutable is great "whenever it is reasonable to do so"... it isn't always.
      $endgroup$
      – BittermanAndy
      17 hours ago






      1




      1




      $begingroup$
      Very interesting answer, thank you! I'll be taking this into consideration in the future!
      $endgroup$
      – Phrancis
      13 hours ago




      $begingroup$
      Very interesting answer, thank you! I'll be taking this into consideration in the future!
      $endgroup$
      – Phrancis
      13 hours ago












      $begingroup$
      Comintern's (good) answer provides some examples of what the exit/connection types might look like.
      $endgroup$
      – BittermanAndy
      7 hours ago




      $begingroup$
      Comintern's (good) answer provides some examples of what the exit/connection types might look like.
      $endgroup$
      – BittermanAndy
      7 hours ago











      6












      $begingroup$

      The other reviews covered many of the main points I'd raise, but there are a handful of others.





      Constructors in abstract classes should be protected, not public:




      public Location(string name) => Name = name;    



      You can't create a new instance of the abstract class, so it is for all intents and purposes protected anyway. Make the access modifier match the behavior.





      I'm not sure that I like some of the naming. For example, CreateObjects() gives only the slightest clue as to what it is doing. I'd probably go with something more along the lines of GenerateMap(). A couple of the member names are also a little ambiguous as to how they function - for example, which room is IHasExteriorDoor.DoorLocation relative to?





      Speaking of doors (keeping in mind that I'm not sure how much was proscribed by the exercise), I also think I find it a bit more natural to have the exits be the primary motive object. It's more natural to me to think of "using a door" than it is to think of "leaving a room". To me, a room is a static thing - it has exits, but you don't really use a room by leaving it. I'd consider building your map from the standpoint of the connections between the locations instead of the locations themselves. Something more like this:



      public interface ILocation
      {
      string Name { get; }
      string Description { get; }
      }




      public interface IExit
      {
      // TODO: my naming sucks too.
      ILocation LocationOne { get; }
      ILocation LocationTwo { get; }
      // Takes the room you're exiting as a parameter, returns where you end up.
      ILocation Traverse(ILocation from);
      }




      public abstract class Location : ILocation
      {
      private readonly IReadOnlyList<IExit> _exits;

      protected Location(string name, string description, IEnumerable<IExit> exits)
      {
      _exits = exits.ToList();
      }

      public IEnumerable<IExit> Exits => _exits;

      // ...other properties...
      }


      That lets you concentrate on the spatial relationships between the locations from a more natural direction (IMO and no pun intended). You'll likely find this to be more easily extensible down the road when you need to, say close or lock a door:



      public interface Door : IExit
      {
      bool IsOpen { get; }
      bool IsLocked { get; }
      }





      share|improve this answer









      $endgroup$


















        6












        $begingroup$

        The other reviews covered many of the main points I'd raise, but there are a handful of others.





        Constructors in abstract classes should be protected, not public:




        public Location(string name) => Name = name;    



        You can't create a new instance of the abstract class, so it is for all intents and purposes protected anyway. Make the access modifier match the behavior.





        I'm not sure that I like some of the naming. For example, CreateObjects() gives only the slightest clue as to what it is doing. I'd probably go with something more along the lines of GenerateMap(). A couple of the member names are also a little ambiguous as to how they function - for example, which room is IHasExteriorDoor.DoorLocation relative to?





        Speaking of doors (keeping in mind that I'm not sure how much was proscribed by the exercise), I also think I find it a bit more natural to have the exits be the primary motive object. It's more natural to me to think of "using a door" than it is to think of "leaving a room". To me, a room is a static thing - it has exits, but you don't really use a room by leaving it. I'd consider building your map from the standpoint of the connections between the locations instead of the locations themselves. Something more like this:



        public interface ILocation
        {
        string Name { get; }
        string Description { get; }
        }




        public interface IExit
        {
        // TODO: my naming sucks too.
        ILocation LocationOne { get; }
        ILocation LocationTwo { get; }
        // Takes the room you're exiting as a parameter, returns where you end up.
        ILocation Traverse(ILocation from);
        }




        public abstract class Location : ILocation
        {
        private readonly IReadOnlyList<IExit> _exits;

        protected Location(string name, string description, IEnumerable<IExit> exits)
        {
        _exits = exits.ToList();
        }

        public IEnumerable<IExit> Exits => _exits;

        // ...other properties...
        }


        That lets you concentrate on the spatial relationships between the locations from a more natural direction (IMO and no pun intended). You'll likely find this to be more easily extensible down the road when you need to, say close or lock a door:



        public interface Door : IExit
        {
        bool IsOpen { get; }
        bool IsLocked { get; }
        }





        share|improve this answer









        $endgroup$
















          6












          6








          6





          $begingroup$

          The other reviews covered many of the main points I'd raise, but there are a handful of others.





          Constructors in abstract classes should be protected, not public:




          public Location(string name) => Name = name;    



          You can't create a new instance of the abstract class, so it is for all intents and purposes protected anyway. Make the access modifier match the behavior.





          I'm not sure that I like some of the naming. For example, CreateObjects() gives only the slightest clue as to what it is doing. I'd probably go with something more along the lines of GenerateMap(). A couple of the member names are also a little ambiguous as to how they function - for example, which room is IHasExteriorDoor.DoorLocation relative to?





          Speaking of doors (keeping in mind that I'm not sure how much was proscribed by the exercise), I also think I find it a bit more natural to have the exits be the primary motive object. It's more natural to me to think of "using a door" than it is to think of "leaving a room". To me, a room is a static thing - it has exits, but you don't really use a room by leaving it. I'd consider building your map from the standpoint of the connections between the locations instead of the locations themselves. Something more like this:



          public interface ILocation
          {
          string Name { get; }
          string Description { get; }
          }




          public interface IExit
          {
          // TODO: my naming sucks too.
          ILocation LocationOne { get; }
          ILocation LocationTwo { get; }
          // Takes the room you're exiting as a parameter, returns where you end up.
          ILocation Traverse(ILocation from);
          }




          public abstract class Location : ILocation
          {
          private readonly IReadOnlyList<IExit> _exits;

          protected Location(string name, string description, IEnumerable<IExit> exits)
          {
          _exits = exits.ToList();
          }

          public IEnumerable<IExit> Exits => _exits;

          // ...other properties...
          }


          That lets you concentrate on the spatial relationships between the locations from a more natural direction (IMO and no pun intended). You'll likely find this to be more easily extensible down the road when you need to, say close or lock a door:



          public interface Door : IExit
          {
          bool IsOpen { get; }
          bool IsLocked { get; }
          }





          share|improve this answer









          $endgroup$



          The other reviews covered many of the main points I'd raise, but there are a handful of others.





          Constructors in abstract classes should be protected, not public:




          public Location(string name) => Name = name;    



          You can't create a new instance of the abstract class, so it is for all intents and purposes protected anyway. Make the access modifier match the behavior.





          I'm not sure that I like some of the naming. For example, CreateObjects() gives only the slightest clue as to what it is doing. I'd probably go with something more along the lines of GenerateMap(). A couple of the member names are also a little ambiguous as to how they function - for example, which room is IHasExteriorDoor.DoorLocation relative to?





          Speaking of doors (keeping in mind that I'm not sure how much was proscribed by the exercise), I also think I find it a bit more natural to have the exits be the primary motive object. It's more natural to me to think of "using a door" than it is to think of "leaving a room". To me, a room is a static thing - it has exits, but you don't really use a room by leaving it. I'd consider building your map from the standpoint of the connections between the locations instead of the locations themselves. Something more like this:



          public interface ILocation
          {
          string Name { get; }
          string Description { get; }
          }




          public interface IExit
          {
          // TODO: my naming sucks too.
          ILocation LocationOne { get; }
          ILocation LocationTwo { get; }
          // Takes the room you're exiting as a parameter, returns where you end up.
          ILocation Traverse(ILocation from);
          }




          public abstract class Location : ILocation
          {
          private readonly IReadOnlyList<IExit> _exits;

          protected Location(string name, string description, IEnumerable<IExit> exits)
          {
          _exits = exits.ToList();
          }

          public IEnumerable<IExit> Exits => _exits;

          // ...other properties...
          }


          That lets you concentrate on the spatial relationships between the locations from a more natural direction (IMO and no pun intended). You'll likely find this to be more easily extensible down the road when you need to, say close or lock a door:



          public interface Door : IExit
          {
          bool IsOpen { get; }
          bool IsLocked { get; }
          }






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered 16 hours ago









          CominternComintern

          3,73211124




          3,73211124























              5












              $begingroup$

              In this code block here:



              ...
              OutsideWithDoor backYard;
              Outside garden;

              public ExploreTheHouseForm()
              {
              InitializeComponent();
              CreateObjects(); // <--- bleh
              MoveToLocation(livingRoom);
              }


              This is call to the CreateObject() method, is something I don't like to see in code (it could be a personal style issue) but if you are constructing an object, then all code related to the construction of an object should just stay in the constructor...



              I would prefer that it ended up looking like



              ...
              private readonly OutsideWithDoor _backYard; // now it can be readonly
              private readonly Outside _garden;

              public ExploreTheHouseForm()
              {
              InitializeComponent();

              ...
              _backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
              _garden = new Outside("garden", false);

              MoveToLocation(livingRoom);
              }





              share|improve this answer









              $endgroup$


















                5












                $begingroup$

                In this code block here:



                ...
                OutsideWithDoor backYard;
                Outside garden;

                public ExploreTheHouseForm()
                {
                InitializeComponent();
                CreateObjects(); // <--- bleh
                MoveToLocation(livingRoom);
                }


                This is call to the CreateObject() method, is something I don't like to see in code (it could be a personal style issue) but if you are constructing an object, then all code related to the construction of an object should just stay in the constructor...



                I would prefer that it ended up looking like



                ...
                private readonly OutsideWithDoor _backYard; // now it can be readonly
                private readonly Outside _garden;

                public ExploreTheHouseForm()
                {
                InitializeComponent();

                ...
                _backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
                _garden = new Outside("garden", false);

                MoveToLocation(livingRoom);
                }





                share|improve this answer









                $endgroup$
















                  5












                  5








                  5





                  $begingroup$

                  In this code block here:



                  ...
                  OutsideWithDoor backYard;
                  Outside garden;

                  public ExploreTheHouseForm()
                  {
                  InitializeComponent();
                  CreateObjects(); // <--- bleh
                  MoveToLocation(livingRoom);
                  }


                  This is call to the CreateObject() method, is something I don't like to see in code (it could be a personal style issue) but if you are constructing an object, then all code related to the construction of an object should just stay in the constructor...



                  I would prefer that it ended up looking like



                  ...
                  private readonly OutsideWithDoor _backYard; // now it can be readonly
                  private readonly Outside _garden;

                  public ExploreTheHouseForm()
                  {
                  InitializeComponent();

                  ...
                  _backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
                  _garden = new Outside("garden", false);

                  MoveToLocation(livingRoom);
                  }





                  share|improve this answer









                  $endgroup$



                  In this code block here:



                  ...
                  OutsideWithDoor backYard;
                  Outside garden;

                  public ExploreTheHouseForm()
                  {
                  InitializeComponent();
                  CreateObjects(); // <--- bleh
                  MoveToLocation(livingRoom);
                  }


                  This is call to the CreateObject() method, is something I don't like to see in code (it could be a personal style issue) but if you are constructing an object, then all code related to the construction of an object should just stay in the constructor...



                  I would prefer that it ended up looking like



                  ...
                  private readonly OutsideWithDoor _backYard; // now it can be readonly
                  private readonly Outside _garden;

                  public ExploreTheHouseForm()
                  {
                  InitializeComponent();

                  ...
                  _backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
                  _garden = new Outside("garden", false);

                  MoveToLocation(livingRoom);
                  }






                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered 23 hours ago









                  SvekSvek

                  766112




                  766112























                      5












                      $begingroup$

                      RobH's review covers syntax and style well so I won't go into that. Instead I'd like to give my take on feedback given by Svek and BittermanAndy.



                      Separation Of Concerns



                      I think Svek's commentary about the CreateObjects method is spot on, but I don't think it goes far enough. The need for such a method in the first place hints that the ExploreTheHouseForm class is doing to much. With the current implementation, each of the rooms is a field on the form. This effectively makes the ExploreTheHouseForm the house itself. As such it would be more aptly named ExplorableHouseForm.



                      In general (and this becomes ever more important as you get into more complex projects) we want to separate the presentation of the data from the data itself.



                      The form is the UI it already has the duty to present the data to the user. It shouldn't also be the data. I would much rather the house be constructed elsewhere and passed to the form's constructor:



                          public ExploreTheHouseForm(Location initialRoom)
                      {
                      InitializeComponent();
                      MoveToLocation(initialRoom);
                      }


                      With this simple change, you can remove all the individual Location fields from ExploreTheHouseForm with the exception of currentLocation. Also, if you desire, you can use the same form to explore any number of different houses without further modification.



                      Immutability



                      A fair amount of BittermanAndy's advice (as of the time of this writing, his post was updated at least once since I started) was to try to make your Location class immutable. Since with the overall design as it is, locations need to reference each other, you run into a chicken & egg scenario preventing immutability where each Location needs their neighbors to be created before them. I don't see a way around this, however if you have your locations implement an interface, and write your form to consume the interface rather than Location you can get much of the same benefit of actual immutability.



                      public interface ILocation
                      {
                      public string Name { get; }
                      public IList<ILocation> Exits {get;}
                      public string Description { get;}
                      }


                      In ILocation we only specify the get portions of the properties. This means to consumers of ILocation the properties are effectively read-only even if the implementing classes implement the set. We also declare Exits as a collection of ILocation rather than Location so that accessed members are also read-only to consumers.



                      You don't have to change much about Location itself:



                      public abstract class Location: ILocation
                      {
                      ...
                      //private field to back Exits property.
                      private IList<ILocation> _exits;

                      public IList<ILocation> Exits {
                      get
                      {
                      // AsReadOnly so that consumers are not allowed to modify contents.
                      // there are other ways of accomplishing this that may be better overall, but ExploreTheHouseForm accesses Exits by index so we can only change it so much.
                      return _exits?.AsReadOnly();
                      }
                      set{ _exits = value;}
                      }
                      }


                      Updating ExploreTheHouseForm is also straight forward, simply change the type of the field currentLocation the Location parameters of ExploreTheHouseForm, MoveToLocation, and ShowGoThroughExteriorDoorButton to ILocation:



                          ...
                      private ILocation _currentLocation;
                      ...
                      public ExploreTheHouseForm(ILocation initialRoom)
                      {
                      InitializeComponent();
                      MoveToLocation(initialRoom);
                      }
                      ...
                      private void MoveToLocation(ILocation location)
                      ...
                      private void ShowGoThroughExteriorDoorButton(ILocation location)


                      The overall impact of this is that the locations are mutable during construction (by some factory) but once construction is complete, all you work with the read-only ILocation



                      // GenerateHouse returns the entry point of the house.

                      public ILocation GenerateHouse()
                      {
                      // Configure the locations
                      var livingRoom = new RoomWithDoor("living room", "an antique carpet", "an oak door with a brass knob");
                      var kitchen = new RoomWithDoor("kitchen", "stainless steel appliances", "a screen door");
                      var diningRoom = new Room("dining room", "a crystal chandelier");
                      var frontYard = new OutsideWithDoor("front yard", false, livingRoom.DoorDescription);
                      var backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
                      var garden = new Outside("garden", false);

                      // Configure the exits
                      livingRoom.Exits = new List<ILocation>() { diningRoom };
                      kitchen.Exits = new List<ILocation>() { diningRoom };
                      diningRoom.Exits = new List<ILocation> { livingRoom, kitchen };
                      frontYard.Exits = new List<ILocation> { backYard, garden };
                      backYard.Exits = new List<ILocation> { frontYard, garden };
                      garden.Exits = new List<ILocation> { frontYard, backYard };

                      // Configure exterior doors
                      livingRoom.DoorLocation = frontYard;
                      frontYard.DoorLocation = livingRoom;
                      kitchen.DoorLocation = backYard;
                      backYard.DoorLocation = kitchen;
                      // return entry point.
                      return livingRoom;

                      }


                      Location Connectivity



                      I agree with the other reviews and comments that pulling the concept of the location connection into a separate class/set of classes would allow for a better design. Locations can have any number of exits, and it is a property of the exit, not the location if that exit is an open archway, a door, or just an abstract dividing line (outdoor location to outdoor location) Comintern does a good job of covering this in their review so I won't go into it further.






                      share|improve this answer









                      $endgroup$









                      • 1




                        $begingroup$
                        Excellent answer. Separating the creation of the house, and presenting the form only with the interface it needs for navigation, solves the creation / immutability problem very nicely. (My edits were fairly minor, by the way).
                        $endgroup$
                        – BittermanAndy
                        7 hours ago
















                      5












                      $begingroup$

                      RobH's review covers syntax and style well so I won't go into that. Instead I'd like to give my take on feedback given by Svek and BittermanAndy.



                      Separation Of Concerns



                      I think Svek's commentary about the CreateObjects method is spot on, but I don't think it goes far enough. The need for such a method in the first place hints that the ExploreTheHouseForm class is doing to much. With the current implementation, each of the rooms is a field on the form. This effectively makes the ExploreTheHouseForm the house itself. As such it would be more aptly named ExplorableHouseForm.



                      In general (and this becomes ever more important as you get into more complex projects) we want to separate the presentation of the data from the data itself.



                      The form is the UI it already has the duty to present the data to the user. It shouldn't also be the data. I would much rather the house be constructed elsewhere and passed to the form's constructor:



                          public ExploreTheHouseForm(Location initialRoom)
                      {
                      InitializeComponent();
                      MoveToLocation(initialRoom);
                      }


                      With this simple change, you can remove all the individual Location fields from ExploreTheHouseForm with the exception of currentLocation. Also, if you desire, you can use the same form to explore any number of different houses without further modification.



                      Immutability



                      A fair amount of BittermanAndy's advice (as of the time of this writing, his post was updated at least once since I started) was to try to make your Location class immutable. Since with the overall design as it is, locations need to reference each other, you run into a chicken & egg scenario preventing immutability where each Location needs their neighbors to be created before them. I don't see a way around this, however if you have your locations implement an interface, and write your form to consume the interface rather than Location you can get much of the same benefit of actual immutability.



                      public interface ILocation
                      {
                      public string Name { get; }
                      public IList<ILocation> Exits {get;}
                      public string Description { get;}
                      }


                      In ILocation we only specify the get portions of the properties. This means to consumers of ILocation the properties are effectively read-only even if the implementing classes implement the set. We also declare Exits as a collection of ILocation rather than Location so that accessed members are also read-only to consumers.



                      You don't have to change much about Location itself:



                      public abstract class Location: ILocation
                      {
                      ...
                      //private field to back Exits property.
                      private IList<ILocation> _exits;

                      public IList<ILocation> Exits {
                      get
                      {
                      // AsReadOnly so that consumers are not allowed to modify contents.
                      // there are other ways of accomplishing this that may be better overall, but ExploreTheHouseForm accesses Exits by index so we can only change it so much.
                      return _exits?.AsReadOnly();
                      }
                      set{ _exits = value;}
                      }
                      }


                      Updating ExploreTheHouseForm is also straight forward, simply change the type of the field currentLocation the Location parameters of ExploreTheHouseForm, MoveToLocation, and ShowGoThroughExteriorDoorButton to ILocation:



                          ...
                      private ILocation _currentLocation;
                      ...
                      public ExploreTheHouseForm(ILocation initialRoom)
                      {
                      InitializeComponent();
                      MoveToLocation(initialRoom);
                      }
                      ...
                      private void MoveToLocation(ILocation location)
                      ...
                      private void ShowGoThroughExteriorDoorButton(ILocation location)


                      The overall impact of this is that the locations are mutable during construction (by some factory) but once construction is complete, all you work with the read-only ILocation



                      // GenerateHouse returns the entry point of the house.

                      public ILocation GenerateHouse()
                      {
                      // Configure the locations
                      var livingRoom = new RoomWithDoor("living room", "an antique carpet", "an oak door with a brass knob");
                      var kitchen = new RoomWithDoor("kitchen", "stainless steel appliances", "a screen door");
                      var diningRoom = new Room("dining room", "a crystal chandelier");
                      var frontYard = new OutsideWithDoor("front yard", false, livingRoom.DoorDescription);
                      var backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
                      var garden = new Outside("garden", false);

                      // Configure the exits
                      livingRoom.Exits = new List<ILocation>() { diningRoom };
                      kitchen.Exits = new List<ILocation>() { diningRoom };
                      diningRoom.Exits = new List<ILocation> { livingRoom, kitchen };
                      frontYard.Exits = new List<ILocation> { backYard, garden };
                      backYard.Exits = new List<ILocation> { frontYard, garden };
                      garden.Exits = new List<ILocation> { frontYard, backYard };

                      // Configure exterior doors
                      livingRoom.DoorLocation = frontYard;
                      frontYard.DoorLocation = livingRoom;
                      kitchen.DoorLocation = backYard;
                      backYard.DoorLocation = kitchen;
                      // return entry point.
                      return livingRoom;

                      }


                      Location Connectivity



                      I agree with the other reviews and comments that pulling the concept of the location connection into a separate class/set of classes would allow for a better design. Locations can have any number of exits, and it is a property of the exit, not the location if that exit is an open archway, a door, or just an abstract dividing line (outdoor location to outdoor location) Comintern does a good job of covering this in their review so I won't go into it further.






                      share|improve this answer









                      $endgroup$









                      • 1




                        $begingroup$
                        Excellent answer. Separating the creation of the house, and presenting the form only with the interface it needs for navigation, solves the creation / immutability problem very nicely. (My edits were fairly minor, by the way).
                        $endgroup$
                        – BittermanAndy
                        7 hours ago














                      5












                      5








                      5





                      $begingroup$

                      RobH's review covers syntax and style well so I won't go into that. Instead I'd like to give my take on feedback given by Svek and BittermanAndy.



                      Separation Of Concerns



                      I think Svek's commentary about the CreateObjects method is spot on, but I don't think it goes far enough. The need for such a method in the first place hints that the ExploreTheHouseForm class is doing to much. With the current implementation, each of the rooms is a field on the form. This effectively makes the ExploreTheHouseForm the house itself. As such it would be more aptly named ExplorableHouseForm.



                      In general (and this becomes ever more important as you get into more complex projects) we want to separate the presentation of the data from the data itself.



                      The form is the UI it already has the duty to present the data to the user. It shouldn't also be the data. I would much rather the house be constructed elsewhere and passed to the form's constructor:



                          public ExploreTheHouseForm(Location initialRoom)
                      {
                      InitializeComponent();
                      MoveToLocation(initialRoom);
                      }


                      With this simple change, you can remove all the individual Location fields from ExploreTheHouseForm with the exception of currentLocation. Also, if you desire, you can use the same form to explore any number of different houses without further modification.



                      Immutability



                      A fair amount of BittermanAndy's advice (as of the time of this writing, his post was updated at least once since I started) was to try to make your Location class immutable. Since with the overall design as it is, locations need to reference each other, you run into a chicken & egg scenario preventing immutability where each Location needs their neighbors to be created before them. I don't see a way around this, however if you have your locations implement an interface, and write your form to consume the interface rather than Location you can get much of the same benefit of actual immutability.



                      public interface ILocation
                      {
                      public string Name { get; }
                      public IList<ILocation> Exits {get;}
                      public string Description { get;}
                      }


                      In ILocation we only specify the get portions of the properties. This means to consumers of ILocation the properties are effectively read-only even if the implementing classes implement the set. We also declare Exits as a collection of ILocation rather than Location so that accessed members are also read-only to consumers.



                      You don't have to change much about Location itself:



                      public abstract class Location: ILocation
                      {
                      ...
                      //private field to back Exits property.
                      private IList<ILocation> _exits;

                      public IList<ILocation> Exits {
                      get
                      {
                      // AsReadOnly so that consumers are not allowed to modify contents.
                      // there are other ways of accomplishing this that may be better overall, but ExploreTheHouseForm accesses Exits by index so we can only change it so much.
                      return _exits?.AsReadOnly();
                      }
                      set{ _exits = value;}
                      }
                      }


                      Updating ExploreTheHouseForm is also straight forward, simply change the type of the field currentLocation the Location parameters of ExploreTheHouseForm, MoveToLocation, and ShowGoThroughExteriorDoorButton to ILocation:



                          ...
                      private ILocation _currentLocation;
                      ...
                      public ExploreTheHouseForm(ILocation initialRoom)
                      {
                      InitializeComponent();
                      MoveToLocation(initialRoom);
                      }
                      ...
                      private void MoveToLocation(ILocation location)
                      ...
                      private void ShowGoThroughExteriorDoorButton(ILocation location)


                      The overall impact of this is that the locations are mutable during construction (by some factory) but once construction is complete, all you work with the read-only ILocation



                      // GenerateHouse returns the entry point of the house.

                      public ILocation GenerateHouse()
                      {
                      // Configure the locations
                      var livingRoom = new RoomWithDoor("living room", "an antique carpet", "an oak door with a brass knob");
                      var kitchen = new RoomWithDoor("kitchen", "stainless steel appliances", "a screen door");
                      var diningRoom = new Room("dining room", "a crystal chandelier");
                      var frontYard = new OutsideWithDoor("front yard", false, livingRoom.DoorDescription);
                      var backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
                      var garden = new Outside("garden", false);

                      // Configure the exits
                      livingRoom.Exits = new List<ILocation>() { diningRoom };
                      kitchen.Exits = new List<ILocation>() { diningRoom };
                      diningRoom.Exits = new List<ILocation> { livingRoom, kitchen };
                      frontYard.Exits = new List<ILocation> { backYard, garden };
                      backYard.Exits = new List<ILocation> { frontYard, garden };
                      garden.Exits = new List<ILocation> { frontYard, backYard };

                      // Configure exterior doors
                      livingRoom.DoorLocation = frontYard;
                      frontYard.DoorLocation = livingRoom;
                      kitchen.DoorLocation = backYard;
                      backYard.DoorLocation = kitchen;
                      // return entry point.
                      return livingRoom;

                      }


                      Location Connectivity



                      I agree with the other reviews and comments that pulling the concept of the location connection into a separate class/set of classes would allow for a better design. Locations can have any number of exits, and it is a property of the exit, not the location if that exit is an open archway, a door, or just an abstract dividing line (outdoor location to outdoor location) Comintern does a good job of covering this in their review so I won't go into it further.






                      share|improve this answer









                      $endgroup$



                      RobH's review covers syntax and style well so I won't go into that. Instead I'd like to give my take on feedback given by Svek and BittermanAndy.



                      Separation Of Concerns



                      I think Svek's commentary about the CreateObjects method is spot on, but I don't think it goes far enough. The need for such a method in the first place hints that the ExploreTheHouseForm class is doing to much. With the current implementation, each of the rooms is a field on the form. This effectively makes the ExploreTheHouseForm the house itself. As such it would be more aptly named ExplorableHouseForm.



                      In general (and this becomes ever more important as you get into more complex projects) we want to separate the presentation of the data from the data itself.



                      The form is the UI it already has the duty to present the data to the user. It shouldn't also be the data. I would much rather the house be constructed elsewhere and passed to the form's constructor:



                          public ExploreTheHouseForm(Location initialRoom)
                      {
                      InitializeComponent();
                      MoveToLocation(initialRoom);
                      }


                      With this simple change, you can remove all the individual Location fields from ExploreTheHouseForm with the exception of currentLocation. Also, if you desire, you can use the same form to explore any number of different houses without further modification.



                      Immutability



                      A fair amount of BittermanAndy's advice (as of the time of this writing, his post was updated at least once since I started) was to try to make your Location class immutable. Since with the overall design as it is, locations need to reference each other, you run into a chicken & egg scenario preventing immutability where each Location needs their neighbors to be created before them. I don't see a way around this, however if you have your locations implement an interface, and write your form to consume the interface rather than Location you can get much of the same benefit of actual immutability.



                      public interface ILocation
                      {
                      public string Name { get; }
                      public IList<ILocation> Exits {get;}
                      public string Description { get;}
                      }


                      In ILocation we only specify the get portions of the properties. This means to consumers of ILocation the properties are effectively read-only even if the implementing classes implement the set. We also declare Exits as a collection of ILocation rather than Location so that accessed members are also read-only to consumers.



                      You don't have to change much about Location itself:



                      public abstract class Location: ILocation
                      {
                      ...
                      //private field to back Exits property.
                      private IList<ILocation> _exits;

                      public IList<ILocation> Exits {
                      get
                      {
                      // AsReadOnly so that consumers are not allowed to modify contents.
                      // there are other ways of accomplishing this that may be better overall, but ExploreTheHouseForm accesses Exits by index so we can only change it so much.
                      return _exits?.AsReadOnly();
                      }
                      set{ _exits = value;}
                      }
                      }


                      Updating ExploreTheHouseForm is also straight forward, simply change the type of the field currentLocation the Location parameters of ExploreTheHouseForm, MoveToLocation, and ShowGoThroughExteriorDoorButton to ILocation:



                          ...
                      private ILocation _currentLocation;
                      ...
                      public ExploreTheHouseForm(ILocation initialRoom)
                      {
                      InitializeComponent();
                      MoveToLocation(initialRoom);
                      }
                      ...
                      private void MoveToLocation(ILocation location)
                      ...
                      private void ShowGoThroughExteriorDoorButton(ILocation location)


                      The overall impact of this is that the locations are mutable during construction (by some factory) but once construction is complete, all you work with the read-only ILocation



                      // GenerateHouse returns the entry point of the house.

                      public ILocation GenerateHouse()
                      {
                      // Configure the locations
                      var livingRoom = new RoomWithDoor("living room", "an antique carpet", "an oak door with a brass knob");
                      var kitchen = new RoomWithDoor("kitchen", "stainless steel appliances", "a screen door");
                      var diningRoom = new Room("dining room", "a crystal chandelier");
                      var frontYard = new OutsideWithDoor("front yard", false, livingRoom.DoorDescription);
                      var backYard = new OutsideWithDoor("back yard", true, kitchen.DoorDescription);
                      var garden = new Outside("garden", false);

                      // Configure the exits
                      livingRoom.Exits = new List<ILocation>() { diningRoom };
                      kitchen.Exits = new List<ILocation>() { diningRoom };
                      diningRoom.Exits = new List<ILocation> { livingRoom, kitchen };
                      frontYard.Exits = new List<ILocation> { backYard, garden };
                      backYard.Exits = new List<ILocation> { frontYard, garden };
                      garden.Exits = new List<ILocation> { frontYard, backYard };

                      // Configure exterior doors
                      livingRoom.DoorLocation = frontYard;
                      frontYard.DoorLocation = livingRoom;
                      kitchen.DoorLocation = backYard;
                      backYard.DoorLocation = kitchen;
                      // return entry point.
                      return livingRoom;

                      }


                      Location Connectivity



                      I agree with the other reviews and comments that pulling the concept of the location connection into a separate class/set of classes would allow for a better design. Locations can have any number of exits, and it is a property of the exit, not the location if that exit is an open archway, a door, or just an abstract dividing line (outdoor location to outdoor location) Comintern does a good job of covering this in their review so I won't go into it further.







                      share|improve this answer












                      share|improve this answer



                      share|improve this answer










                      answered 15 hours ago









                      Mr.MindorMr.Mindor

                      27114




                      27114








                      • 1




                        $begingroup$
                        Excellent answer. Separating the creation of the house, and presenting the form only with the interface it needs for navigation, solves the creation / immutability problem very nicely. (My edits were fairly minor, by the way).
                        $endgroup$
                        – BittermanAndy
                        7 hours ago














                      • 1




                        $begingroup$
                        Excellent answer. Separating the creation of the house, and presenting the form only with the interface it needs for navigation, solves the creation / immutability problem very nicely. (My edits were fairly minor, by the way).
                        $endgroup$
                        – BittermanAndy
                        7 hours ago








                      1




                      1




                      $begingroup$
                      Excellent answer. Separating the creation of the house, and presenting the form only with the interface it needs for navigation, solves the creation / immutability problem very nicely. (My edits were fairly minor, by the way).
                      $endgroup$
                      – BittermanAndy
                      7 hours ago




                      $begingroup$
                      Excellent answer. Separating the creation of the house, and presenting the form only with the interface it needs for navigation, solves the creation / immutability problem very nicely. (My edits were fairly minor, by the way).
                      $endgroup$
                      – BittermanAndy
                      7 hours ago


















                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to Code Review Stack Exchange!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      Use MathJax to format equations. MathJax reference.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211662%2fhouse-plan-design-head-first-c%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      Liquibase includeAll doesn't find base path

                      How to use setInterval in EJS file?

                      Petrus Granier-Deferre