I'm attempting to write an application that solves nanograms or picross puzzles. I'm using C# and would like to take an object-oriented approach, as I'm quite lacking in that area and would like practice. I'm writing to first make it work in the Windows console, but afterwards would like to add a GUI using WPF.
The problem is that I cannot figure out a design that follows all the "rules" that I've been finding regarding well-designed OOP programs. When I say this, I am specifically referring to encapsulation, single-responsibility principle, and abstraction.
The classes of importance I'm working with include the following. I've only mentioned the main functions or properties of interest:
// This represents a single row or column within a PicrossTable.
// Length is the amount of blocks and blockGroupSizes is the
// amount of shaded groups of blocks.
public PicrossSegment(int length, params int[] blockGroupSizes)
string[] blocks;
BlockGroup[] blockGroups;
public int Length { get; }
public int SolvedGroups { get; }
public bool IsSolved { get; }
// This represents a group of shaded blocks in a PicrossSegment
// I need to keep track of when all groups have been accounted for
// in the PicrossSegment, and it makes sense to put it here
public BlockGroup(int size)
public int Size { get; }
public int BlocksMarked { get; set; }
public bool IsSolved { get; }
// This is the class that would actually contain all the different
// PicrossSegments.
public PicrossTable(int rows, int cols)
public PicrossTable.AddRow(PicrossSegment ps)
public PicrossTable.AddCol(PicrossSegment ps)
private PicrossSegment[] rowData;
private PicrossSegment[] colData;
// This is the class that would solve the puzzle.
public PicrossSolver()
public PicrossSolver.SolveTable(PicrossTable pt)
// This would represent a single action taken by PicrossSolver,
// such as marking a block in any given PicrossSegment.
// layoutIndex would refer to the Row or Column, pos would refer to
// the actual position within that segment
public Step(SegmentLayout layout, int layoutIndex, int pos)
public enum SegmentLayout {Row, Col}
// This would contain a list of all the steps taken and in the
// taken order, allowing for replaying of step-by-step solutions
// of how the puzzle was actually solved.
public SolutionLogger
public SolutionLogger.AddStep(Step s);
public SolutionLogger.PrintSteps();
I find myself in two situations here.
(1) The code that I've shown above requires that I break several layers of encapsulation. PicrossSegment() contains private members blocks and blockGroups, which would need to be accessible in PicrossSolver. This means that PicrossSolver has to reach into PicrossTable and then pull out the PicrossSegments. This seems bad to me because at worst it makes PicrossTable look like a useless middle man but it's actually supposed to allow PicrossSegments to exchange data (akin to solving a row of a crossword puzzle by using a letter in a connecting column). It's also supposed to burden the responsibility of designating which PicrossSegments represent rows or columns so that the SolutionLog can specify as such. It would make no sense for PicrossSegments to know what they are. This approach seems to lead me to violating encapsulation, as PicrossSegment's private data is essentially used by a total of three classes.
(2) I tried experimenting with simply having PicrossSegments know how to solve themselves, and again use PicrossTable to merge data between all the Segments. However, this will very easily lead to a God class situation. All of the main logic of the program will exist in PicrossSegment(), which will make adding additional functionality to the code a bit more difficult as it will have to play around that. Additionally, it seems to violate the single-responsibility principle because it will be updating for multiple reasons (self-solving, info from other segments, data validation). Also, this just feels wrong, because if I would want to expand this program into a game where users could play Picross puzzles on their own, then PicrossSegments() really should just be data set by the UI with no solving behavior.
I've tried turning to Head First Design Patterns, as I'm obviously not refined in OOP and was looking for some structure ahead of time (while keeping in mind it's a common fallacy for beginners to attempt to apply patterns where they don't belong), but I couldn't even find anything that is relevant to me. It almost seems to me like an OOP approach to this program is overkill. I don't see where I need abstract or reuse. Perhaps in PicrossSolver, where I can abstract the concept of solving into a few classes that solve in a specific way.
Long-winded narrative aside I come back to the central question, is there an obvious way to do this without breaking encapsulation or single-responsibility principle?
Aucun commentaire:
Enregistrer un commentaire