Let say I own a resturant that makes a pie. I want to cut this pie according to the input
parsed from the file. That hold the key to the information on how the Pie is to be cut. I use
CircleDividerParser class
to parse the file.txt.
file format:
- pie_cuttin_style_name = is key for searching it back
- radius = radius or pie
- x = x-coordinate of pie center
- y = y-coordinate of pie center
- angle_type = angle can be either degree or radian
- angle_value = value of angle that could be radian or degree
- direction = clock wise or anti-clockwise angle
- from_x = x point in circle from where the angle is measured
- from_y = y point in circle from where the angle is
measured
eg: file.txt
cutting_pattern_1,5,1,2,degree,40,anti-clock,6,2
....
..
Then I have class for doing the parsing logic:
Class CircleDividerParser{
// does the parsing logic to capture the values
void Dowork(filename){
std::ifstream file_stream.open(filename);
std::string line;
while(std::getline(file_stream, line){
std::string str;
std::stringstream ss(line);
int i = 0;
while(std::getline(ss, str, ',')){
holder[i] = str;
i++;
}
pcc.add_pie_cutting_info(holder);
}
}
private:
std::array<std::string, 9> holder;
PieCutterCreator pcc;
};
// This class does the creation of PieCuttingInfo based on the array of string provided from CicleDividerParser
class PieCutterCreator{
public:
void add_pie_cutting_info(std::array<std::string, 9> arr){
PieCuttingInfo obj;
obj.AddPieCuttingStyleName(arr[0]);
obj.AddRadius(arr[1]);
...
..
}
PieCuttingInfo GetPieCuttingInfo(const std::string& name_to_find);
std::vector<PieCuttingInfo> GetAllPieCuttingInfo() { return collect;}
private:
std::vector<PieCuttingInfo> collect;
};
class PieCuttingInfo{
class enum angle_type {Degree, Radian};
public:
PieCuttingInfo()=default;
void AddPieCuttingStyleName(const std::string& name){ pie_cutting_style_name = name;}
void AddRaius(const std::string& r) {radius = r;}
void AddAngleType(const std::string& at) {
if(at == "degree"){
angle_value = angle_type::Degree;
}else if(at == "radian"){
angle_value = angle_type::Radian;
}else {// error}
}
...
private:
std::string pie_cutting_style_name;
std::string radius;// radius or pie
std::string x;// x-coordinate of pie center
std::string y;// y-coordinate of pie center
std::string angle_type; angle can be either degree or radian
std::string angle_value; value of angle that could be radian or degree
std::string direction; clock wise or anti-clockwise angle
std::string from_x; x point in circle from where the angle is measured
std::string from_y; y point in circle from where the angle is measured
};
class PieCuttingMaching(){
void DoAllThePieCutting(){
save = obj.GetAllPieCuttingInfo();
for(auto& it: save){
// use the info from PieCuttingInfo to do the job
}
}
private:
std::vector<PieCuttingInfo> save;
PieCutterCreator obj;
}
Questions :
- Is the design I am following correct or am I making things compilcated
- Can a class PieCuttingInfo be created even if all the information is not provided.
- Should the variables the PieCuttingInfo class be initialized during its creation by passing all the variable at once.
1 Answer 1
Well, what you see before you is really a breakfast food cooker. Joking aside, it sounds like your solution is allowing room for mistakes in your read file, and your program would have trouble determining potential mistakes of this nature. I can't say because perhaps the machine slicing the pies need all this information and you want to give maximum control to the person writing the file.
There's nothing wrong with this per se, but it's very low level. That is to say, the person writing the parameters has to get everything right, not you. If this were input to a machine, there is serious possibility of making the machine do things that it can't do or attempts to do and ends up destroying itself in the process.
Simplification is key
These may be the actual parameters of the machine, but my advice to you would be to simplify pie cutting operations into use cases, and translate the "simple" parameters into the more complex ones that your machine can execute. Start with an abstract CuttingInfo
class with the method GetMachineParams
. Any class deriving from CuttingInfo
takes as input simplified parameters and its output to GetMachineParams
are the low level details to pass to the machine.
You can likewise create an abstract Parser
class with methods CanRead
and Read
. CanRead
can be called with the current line of the file to determine if it can be deserialized by that class, and Read
is consequently called when CanRead
returns true which returns an instance of CuttingInfo
.
Use Case - 8 slices
I want the machine to divide my pie into 8 even slices. This is going to be a popular request no doubt. If not 8 slices, then 10 or 6, but nonetheless the user of your program aka your client, isn't going to talk about the number of radians per slice or moving counterclockwise.
Class NEvenSlicesCuttingInfo
takes as a parameter only a number presenting N. The actual work of translating this into a series of instructions, goes into GetMachineParams
.
You then only need a Class NEvenSlicesParser
which returns true on CanRead
if and only if the line starts with "NEvenSlices". The second parameter on the same line must be N. So the line in the file should be:
NEvenSlices 8
The job of NEvenSlicesParser
in Read
should be nothing more sophisticated than reading the parameter after NEvenSlices
and returning a new NEvenSlicesCuttingInfo(numberRead)
.
Granted, the file could still contain improper parameters in this way, but you can check the simplified parameters. Assuming your class does as it should, you don't need to worry about breaking the machine with these parameters.
Bringing it together
To use it, in your program you would have a vector of Parser
classes instantiated once on program start with all implementations of Parser
. As you read the file, line by line, you pass to CanRead
. If it returns true, call its Read
method and receive a CuttingInfo
that you can call at any time to retrieve machine parameters. All of this without knowing the implementation details in each implementation class.
You can see clearly you can expand on this to add other use cases. You can even cover your original scenario by creating a 1 to 1 parser that loads every possible parameter and simply returns it in GetMachineParams
should you require absolute control that doesn't break this pattern if you require it (though I would strongly discourage the use of such a class).
This approach has the disadvantage that you would have to rewrite the instruction file to contain all information on the same line, but nothing's keeping you from creating a more sophisticated Parser which has some sort of convention for writing instructions on multiple lines (i.e. if you see line start with <<<
, read until you find line ending with >>>
and return contents in the middle to Parser.CanRead
).
The crucial thing is that your program logic no longer cares about implementation details, allowing you maximum flexibility and easy future maintenance.