open closed principle c# - c#

I have 3 classes (1 absrtact(Services), and 2 derivatives). Plus, I have 2 different output formats (UA/EN). And I don't know how remake my code in order it follow a open closed principle. For example, if I want to add a German output format. I will need to edit each class.
using System;
using System.Globalization;
namespace naslidov
{
public abstract class Services
{
public string title;
public decimal price;
public Services(string title, decimal price)
{
this.title = title;
this.price = price;
}
public virtual string ToEnglish()
{
return $" ";
}
public virtual string ToUkraine()
{
return $"";
}
}
public class Food : Services
{
public DateTime expirationDate;
public Food(string title, decimal price, DateTime expirationDate)
: base(title, price)
{
this.title = title;
this.price = price;
this.expirationDate = expirationDate;
}
public override string ToEnglish()
{
return $"{base.title} |{price.ToString("N", CultureInfo.InvariantCulture)} uan | {expirationDate.ToString("d", CultureInfo.CreateSpecificCulture("ja-JP"))} |------ ";
}
public override string ToUkraine()
{
return $"{base.title} |{price.ToString("F", CultureInfo.InvariantCulture).Replace(".", ",")} uan | {expirationDate.ToString("dd.MM.yyyy")}| ------ ";
}
}
public class HouseholdAppliance : Services
{
public int warrantyPeriodInMonths;
public HouseholdAppliance(string title, decimal price, int warrantyPeriodInMonths)
: base(title, price)
{
this.title = title;
this.price = price;
this.warrantyPeriodInMonths = warrantyPeriodInMonths;
}
public override string ToEnglish()
{
return $"{base.title} |{price.ToString("N", CultureInfo.InvariantCulture)} uan | ------ |{warrantyPeriodInMonths} ";
}
public override string ToUkraine()
{
return $"{base.title} |{price.ToString("F", CultureInfo.InvariantCulture).Replace(".", ",")} uan | ------ |{warrantyPeriodInMonths} ";
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Enter region(UA/EN):");
string user_input = Console.ReadLine();
DateTime date1 = new DateTime(2002, 3, 25);
DateTime date2 = new DateTime(2022, 8, 17);
DateTime date3 = new DateTime(2005, 1, 10);
Services first = new Food("apple", 105324660120.58m, date1);
Services second = new Food("bananas", 3045.21m, date2);
Services third = new Food("nuts", 308540m, date3);
Services nofrst = new HouseholdAppliance("television", 25547.54m, 12);
Services noscd = new HouseholdAppliance("pilosos", 2756854m, 24);
Services nothir = new HouseholdAppliance("notebook", 32248, 36);
Services[] fullservices = new Services[] { first, second, third, nofrst, noscd, nothir };
Console.WriteLine("title | price | expirationDate | warrantyPeriodInMonths");
Console.WriteLine("-----------------------------------------------------------------------");
if (user_input == "EN")
{
foreach (Services fullservice in fullservices)
{
Console.WriteLine(fullservice.ToEnglish());
}
}
if (user_input == "UA")
{
foreach (Services fullservice in fullservices)
{
Console.WriteLine(fullservice.ToUkraine());
}
}
else if (user_input != "UA" && user_input != "EN")
{
Console.WriteLine(" Sorry, wrong input!");
}
}
}
}

First of all, I want to encourage to never refactor without a requirement or goal. If you are trying to refactor this code to make it extensible for something you don't know needs to be extended, you are not only likely to be wasting effort (YAGNI), but you may also end up with code that is harder to change in other ways you might need later.
Thus, for the purposes of this answer, I will assume that the requirement is to make this code extensible (open for extension). And that what you need to extend is the supported formats.
We will start by defining a new abstract class Formatter interface IFormat which will serve as extension point to add new format. Ideally, this IFormat should not depend on any specific (concrete, not abstract) Services, nor should Services know about any specific IFormat. That is, we want extending these to be as independent as possible.
Now, what do specific Services need to format? I can see in the code that you need to know the format for dates and prices. So let us give methods to format those to IFormat:
public interface IFormat
{
string FormatDate(DateTime date);
string FormatPrice(decimal price);
}
Add any other methods that make sense. I added the minimum for this case.
We can proceed to implement a formatter for English and one for Ukrane. Please excuse my naming convention.
public class FormatterEnglish : IFormat
{
public string FormatDate(DateTime date)
{
return date.ToString("d", CultureInfo.CreateSpecificCulture("ja-JP"));
}
public string FormatPrice(decimal price)
{
return price.ToString("N", CultureInfo.InvariantCulture);
}
}
public class FormatterUkrane : IFormat
{
public string FormatDate(DateTime date)
{
return date.ToString("dd.MM.yyyy");
}
public string FormatPrice(decimal price)
{
return price.ToString("F", CultureInfo.InvariantCulture).Replace(".", ",");
}
}
Now, let us rework Services to use it. It will no longer have one method per format, but one single method that takes a IFormat argument:
public abstract class Services
{
public decimal price;
public string title;
public Services(string title, decimal price)
{
this.title = title;
this.price = price;
}
public abstract string ToString(IFormat formatter);
}
And, of course, we need to implement it in HouseholdAppliance:
public override string ToString(IFormat formatter)
{
return $"{base.title} |{formatter.FormatPrice(price)} uan | ------ |{warrantyPeriodInMonths} ";
}
And Food:
public override string ToString(IFormat formatter)
{
return $"{base.title} |{formatter.FormatPrice(price)} uan | {formatter.FormatDate(expirationDate)} |------ ";
}
To choose our IFormat, I suggest a factory method. For example:
private static IFormat? CreateFormatter(string formatName)
{
if (formatName == "EN")
{
return new FormatterEnglish();
}
if (formatName == "UA")
{
return new FormatterUkrane();
}
return null;
}
You may also be interested in using type discovery, and specifying the format name in a custom attribute. That is beyond the scope of this answer.
Finally, you can use it like this:
var formatter = CreateFormatter(user_input);
if (formatter == null)
{
Console.WriteLine(" Sorry, wrong input!");
return;
}
foreach (Services fullservice in fullservices)
{
Console.WriteLine(fullservice.ToString(formatter));
}
Having a second look at the code, it is possible to extract the format template from the Services. The IFormat would need the template and the data. A solution like FormatWith would make that easier. Anyway, I believe this answer addresses the question as it is.

Related

CS7036:There is no argument given that corresponds to the required formal parameter 'i' [duplicate]

This question already has answers here:
C# inheritance and default constructors
(4 answers)
Inheritance with base class constructor with parameters [duplicate]
(2 answers)
Closed 1 year ago.
I am just learning C#, and I made two external classes with constructors, and one inherits from another one. But it is giving the Error:
Severity Code Description Project File Line Suppression State
Error CS7036 There is no argument given that corresponds to the required formal parameter 'i' of 'Engineer.Engineer(string)' program.cs C:\Users\win 10\Desktop\C#\program.cs\program.cs\Car.cs 41 Active
The Three Code files are:
1/ main.cs:
using System;
namespace program
{
class Core
{
static void Main(string[] args)
{
Car BMW = new Car("X-A-21-A-X", 3200000, "Reddish-brown", false);
string currentPrice = BMW.CheckPrice("us", BMW.price);
if(!double.TryParse(currentPrice, out var q))
{
Console.WriteLine(currentPrice);
}else if(double.TryParse(currentPrice, out var z))
{
double converted_Price = Convert.ToDouble(currentPrice);
Console.WriteLine(converted_Price);
}
Console.WriteLine(BMW.model);
}
}
}
2/ Car.cs:
using System;
namespace program
{
class Car : Engineer
{
private string _model;
public string model
{
get { return _model; }
set { _model = value; }
}
public double price;
public string color;
public bool available;
public string CheckPrice(string locale, double price)
{
string ret = default(string);
if(locale == "in")// India
{
ret = Convert.ToString(2.14 * price);
}else if(locale == "us")// USA
{
ret = Convert.ToString(3.98 * price);
}else if(locale == "jp")// Japan
{
ret = Convert.ToString(1.3 * price);
}else if(locale == "vn")//Vietnam
{
ret = Convert.ToString(0.78645 * price);
}else if(locale == "ch")//China
{
ret = Convert.ToString(2.56 * price);
}
else
{
ret = "Invalid Locale, Your Country does not ship the car.";
}
Console.WriteLine(_model);
return ret;
}
public Car(string modelName, double priceVal, string ColorName, bool avail) /* 'Car' in this line is causing problems*/
{
model = modelName;
price = priceVal;
color = ColorName;
available = avail;
}
}
}
3/ Engineer.cs:
using System;
namespace program
{
class Engineer
{
private string creatorCompany;
public string creator_Company
{
get { return creatorCompany; }
set { creatorCompany = value; }
}
public Engineer(string i)
{
creator_Company = i;
}
}
}
There are answers there but I can't understand them. Please explain them to me like I'm a monke who doesn't know sh*t
You need to add the default constructor to the Engineer class. because when you create an instance of derived it calls the base class constructor before the derived class constructor.
public Engineer()
{
}
If Car is Engineer
In the unlikely scenario that Car is Engineer the Car needs to supply creatorCompany:
Engineer definition states that creatorCompany must be supplied
Car is Engineer
Car must provide creatorCompany.
It could look something like this:
public Car(
string creatorCompany, // Added
string modelName,
double priceVal,
string ColorName,
bool avail)
: base(i: creatorCompany) // Added
{
model = modelName;
price = priceVal;
color = ColorName;
available = avail;
}
If Car is not Engineer
In this case, the solution is to remove : Engineer:
class Car : Engineer
becomes:
class Car
In the constructor of the child class you must reference the parameters of the parent/base class.
In this case change the constructor of the Car class to the following.
//Inside class Car
public Car(string i, string modelName, double priceVal, string ColorName, bool avail) : base(i)
{
//Code inside
}
This is all that cause an issue not anything else.

Set Numbering Format of C# application to Indian numbering System

I have many classes with int, decimal variables in my C# application.
Consider the following example.
public class Employee
{
decimal TotalSalary;
public decimal Salary
{
get
{
return TotalSalary;
}
set
{
TotalSalary = value;
}
}
public string GetSalary()
{
return TotalSalary.ToString();
}
}
public class Contract
{
Employee emp1 = new Employee();
public void ProcessSalary()
{
emp1.Salary = 100000;
SendToLabel(emp1.GetSalary());
}
}
In the above example whenever I use "ToString" of any decimal/int variable in my application, it should give me the number in Indian numbering format like below.
100000 should render as 1,00,000
10000 should render as 10,000
This should happen globally in my C# .NET application.
Can I do this using CultureInfo in global.asax page.
Right now for formatting date i am using the following code.
CultureInfo newCulture = (CultureInfo) System.Threading.Thread.CurrentThread.CurrentCulture.Clone();
newCulture.DateTimeFormat.ShortDatePattern = "dd-MMM-yyyy";
newCulture.DateTimeFormat.DateSeparator = "-";
Thread.CurrentThread.CurrentCulture = newCulture;
Following code will be useful to you,
public string GetSalary()
{
CultureInfo inr = new CultureInfo("hi-IN");
return string.Format(inr, "{0:#,#}", TotalSalary);
}
whenever I use "ToString" of any decimal/int variable in my application, it should give me the number in Indian numbering format
You could also make a new method as extension for the types decimal and int:
public static class MyExtensions
{
public static string MyOutput(this decimal number)
{
return number.ToString("#,#.##", new CultureInfo(0x0439));
}
public static string MyOutput(this int number)
{
return number.ToString("#,#", new CultureInfo(0x0439));
}
}
All culture codes for further reference.
Then you can use it throughout your programm for variables of the decimal/int types:
public string GetSalary()
{
return TotalSalary.MyOutput();
}
Output: for decimal asd = 1000000.23m;
10,00,000.23
The signature of the GetSalary will be like this:
public string GetSalary()
{
return String.Format("{0:n}", TotalSalary);
}
Working Example
You can use "{0:n3}" if you want to round off decimals to 3 digits.

Read values from a non-delimited string into class object

I have a string with the following structure:
Student Name________AgeAddress_______________________Bithday___Lvl
Example:
Jonh Smith 016Some place in NY, USA 01/01/2014L01
As you can see, there is no delimited character like | or ,
Also, there is no space between fields (if you check, there is no space between Age/Address and Birthday/Level.
The size of each field is static so if data's length is less then it will contains white spaces.
I have a class that need to be filled with that information:
public class StudentData
{
public char[] _name = new char[20];
public string name;
public char[] _age = new char[3];
public string age;
public char[] _address = new char[30];
public string address;
public char[] _bday = new char[10];
public string bday;
public char[] _level = new char[3];
public string level;
}
Is there any way to do this automatically and dynamically?
I mean I really don't want to code like this:
myClass.name = stringLine.substring(0,19);
myClass.age = stringLine.substring(20,22);
That's because I have way more fields that the ones added in this example & way more string lines with other different data.
Update: There were supposed to be a lot of spaces between "Smith" and "016", but I don't know how to edit it.
Update2: If I use StringReader.Read() I can evade to use substring and indexes, but it isn't still so dynamically because I would need to repeat those 3 lines for each field.
StringReader reader = new StringReader(stringLine);
reader.Read(myClass._name, 0 myClass._name.Length);
myClass.name = new string(myClass._name);
Given your requirement I came up with an interesting solution. All be-it it may be more complex and longer than using the String.SubString() method as stated.
However this solution is transferable to other types and other string. I used a concept of Attributes, Properties, and Reflection to parse a string by a Fixed Length and setting the class Properties.
Note I did change your StudentData class to follow a more conventional coding style. Following this handy guide on MSDN: http://msdn.microsoft.com/en-us/library/xzf533w0(v=vs.71).aspx
Here is the new StudentData class. Note it uses the properties as opposed to fields. (Not discussed here).
public class StudentData
{
string name;
string age;
string address;
string bday;
string level;
[FixedLengthDelimeter(0, 20)]
public string Name { get { return this.name; } set { this.name = value; } }
[FixedLengthDelimeter(1, 3)]
public string Age { get { return this.age; } set { this.age = value; } }
[FixedLengthDelimeter(2, 30)]
public string Address { get { return this.address; } set { this.address = value; } }
[FixedLengthDelimeter(3, 10)]
public string BDay { get { return this.bday; } set { this.bday = value; } }
[FixedLengthDelimeter(4, 3)]
public string Level { get { return this.level; } set { this.level = value; } }
}
Note on each of the properties there is an Attribute called FixedLengthDelimeter that takes two parameters.
OrderNumber
FixedLength
The OrderNumber parameter denotes the order in the string (not the position) but the order in which we process from the string. The second parameter denotes the Length of the string when parsing the string. Here is the full attribute class.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class FixedLengthDelimeterAttribute : Attribute
{
public FixedLengthDelimeterAttribute(int orderNumber, int fixedLength)
{
this.fixedLength = fixedLength;
this.orderNumber = orderNumber;
}
readonly int fixedLength;
readonly int orderNumber;
public int FixedLength { get { return this.fixedLength; } }
public int OrderNumber { get { return this.orderNumber; } }
}
Now the attribute is simple enough. Accepts the two paramters we discussed eariler in the constructor.
Finally there is another method to parse the string into the object type such as.
public static class FixedLengthFormatter
{
public static T ParseString<T>(string inputString)
{
Type tType = typeof(T);
var properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public); //;.Where(x => x.GetCustomAttributes(typeof(FixedLengthDelimeterAttribute), false).Count() > 0);
T newT = (T)Activator.CreateInstance(tType);
Dictionary<PropertyInfo, FixedLengthDelimeterAttribute> dictionary = new Dictionary<PropertyInfo, FixedLengthDelimeterAttribute>();
foreach (var property in properties)
{
var atts = property.GetCustomAttributes(typeof(FixedLengthDelimeterAttribute), false);
if (atts.Length == 0)
continue;
dictionary[property] = atts[0] as FixedLengthDelimeterAttribute;
}
foreach (var kvp in dictionary.OrderBy(x => x.Value.OrderNumber))
{
int length = kvp.Value.FixedLength;
if (inputString.Length < length)
throw new Exception("error on attribute order number:" + kvp.Value.OrderNumber + " the string is too short.");
string piece = inputString.Substring(0, length);
inputString = inputString.Substring(length);
kvp.Key.SetValue(newT, piece.Trim(), null);
}
return newT;
}
}
The method above is what does the string parsing. It is a pretty basic utility that reads all the properties that have the FixedLengthDelimeter attribute applied a Dictionary. That dictionary is then enumerated (ordered by OrderNumber) and then calling the SubString() method twice on the input string.
The first substring is to parse the next Token while the second substring resets the inputString to start processing the next token.
Finally as it is parsing the string it is then applying the parsed string to the property of the class Type provided to the method.
Now this can be used simply like this:
string data1 = "Jonh Smith 016Some place in NY, USA 01/01/2014L01";
StudentData student = FixedLengthFormatter.ParseString<StudentData>(data1);
What this does:
Parses a string against property attributes in a fixed length format.
What this does not do:
It does convert the parsed strings to another type. Therefore all the properties must be a string. (this can be easily adapted by adding some type casting logic in).
It is not well tested. This is only tested against a few samples.
It is not by all means the only or best solution out there.
You could use FileHelpers library (NuGet).
Just define the structure of your input file with attributes:
[FixedLengthRecord]
public class StudentData
{
[FieldFixedLength(20)]
[FieldTrim(TrimMode.Right)]
public string name;
[FieldFixedLength(3)]
public string age;
[FieldFixedLength(30)]
[FieldTrim(TrimMode.Right)]
public string address;
[FieldFixedLength(10)]
public string bday;
[FieldFixedLength(3)]
public string level;
}
Then simply read the file using FileHelperEngine<T>:
var engine = new FileHelperEngine<StudentData>();
var students = engine.ReadFile(filename);

Classes convertor

I have the following class People:
class People
{
public enum Gender
{
Man = 'w',
Woman = 'f'
}
public struct Person
{
public string First, Last;
public Gender Gender;
public int Age;
public float Height, Weight;
}
public struct Group
{
public int Members;
public string Name;
}
}
Now, we are located in the class Program:
class Program
{
static void Main( string[] args )
{
People.Person p = new People.Person();
p.First = "Victor";
p.Last = "Barbu";
p.Age = 14;
p.Height = 175;
p.Weight = 62;
p.Gender = People.Gender.Man;
Console.ReadLine();
}
}
I want to put a line like this:
Console.Write( x.toString() );
How can I customize the x.toString() method, so the result to be the following in the Console
Victor Barbu
14 years
Man
175 cm
62 kg
?
Thanks in advance!
Just overrive the ToString() method
public override string ToString()
{
// return the value you want here.
}
You want to override the ToString method in the Person class. See: http://msdn.microsoft.com/en-us/library/ms173154(v=vs.80).aspx
In your case
public class Person
{
// Snip
public override string ToString()
{
return this.First + " " + this.Last;
}
}
If you then do
Console.WriteLine(person.ToString());
The expected output would be the first and last name, you can obviously extend this to include your other fields and line breaks, etc.
Side note; what you are doing is really "pretty printing" I would suggest creating a static method "public static string PrettyPrintPerson(Person p)" or similar, to deal with textual formatting of the class.
class People
{
public override string ToString()
{
//This will return exactly what you just put down with line breaks
// then just call person.ToString()
return string.format("{1} {2}{0}{3} years{0}{4}{0}{5} cm{0}{6} kg", Environment.NewLine,
First, Last, Age, Gender, Height, Weight);
}
}

Possible to switch out a value that's being returned in an if/else method?

I have the following code in a Calculations.cs class:
public decimal decPaymentPlan(QuoteData quoteData)
{
if (quoteData.StepFilingInformation.PaymentPlanRadioButton
== StepFilingInformation.PaymentPlan.No)
return PriceQuote.priceNoPaymentPlan;
else
return PriceQuote.pricePaymentPlanChapter7; //may want to switch
//to Chapter13 value
}
public decimal CalculateChapter7(QuoteData quoteData)
{
decimal total = PriceQuote.priceChapter7;
total += this.decPaymentPlan(quoteData); //want to be able to tell
//which to use, 7 or 13
return total;
}
I am trying to see if I can avoid an extra decPaymentPlan where the final return is pricePaymentPlanChapter13. I thought there might be a way to switch it out.
Otherwise, I'd have to do the following:
public decimal decPaymentPlanChapter7(QuoteData quoteData)
{
...
else
return PriceQuote.pricePaymentPlanChapter7;
}
public decimal decPaymentPlanChapter13(QuoteData quoteData)
{
...
else
return PriceQuote.pricePaymentPlanChapter13;
}
...
//the following will appear anyway, but rather than just using
//one method call which switches the choice based on something
public decimal CalculateChpater7(QuoteData quoteData)
{
...
//instead of decPaymentPlan(quoteData) + something to switch
total+= this.decPaymentPlanChapter7(quoteData);
...
}
public decimal CalculateChpater13(QuoteData quoteData)
{
...
//instead of decPaymentPlan(quoteData) + something to switch
total+= this.decPaymentPlanChapter13(quoteData);
...
}
Is something like this doable (and how)? Thanks. Appreciate any code samples or guidance.
UPDATE:
This is my controller:
public ActionResult EMailQuote()
{
Calculations calc = new Calculations();
QuoteData quoteData = new QuoteData
{
StepFilingInformation = new Models.StepFilingInformation
{
//just moking user input here temporarily to test out the UI
PaymentPlanRadioButton = Models.StepFilingInformation.PaymentPlan.Yes,
}
};
var total = calc.CalculatePrice(quoteData);
ViewBag.CalculatePrice = total; // ADDED THIS LINE
return View(quoteData);
}
Also, I set a value in PriceQuote for Chapter7 and Chapter 13 (e.g., public static decimal priceChapter7 { get { return 799; } }
Hard to be sure of a suggestion without understanding more about what you are doing, but if the only difference between your methods are a set of values to use (one set for chapter7, the other for chapter13) it may make sense to take these values out of PriceQuote and create a base type to hold these values. Then your decPaymentPlan and other methods would only take an instance of that type. For example:
class Chapter // for lack of a better name
{
public decimal PaymentPlan { get; set; }
public decimal Price { get; set; }
....
}
Then, change your methods to take a Chapter parameter
public decimal decPaymentPlan(QuoteData quoteData, Chapter chapter)
{
if (quoteData.StepFilingInformation.PaymentPlanRadioButton
== StepFilingInformation.PaymentPlan.No)
return PriceQuote.priceNoPaymentPlan;
else
return chapter.PaymentPlan;
}
public decimal Calculate(QuoteData quoteData, Chapter chapter)
{
decimal total = chapter.Price;
total += this.decPaymentPlan(quoteData, chapter);
return total;
}
Now all you would need are two instances of Chapter, one for 7 and the other for 13, and call your calculate method accordingly.
UPDATE: To elaborate a bit on what I mean by 'call your calculate method accordingly', lets say for example you had two static variables (somewhere that makes sense in your application, perhaps in Calculations.cs)
static Chapter Chapter7 = new Chapter() { Price = 799.99, PaymentPlan = 555.55 };
static Chapter Chapter13 = ...
Then in your controller, you would be able to write
ViewBag.Chapter7Total = calc.CalculatePrice(quoteData, Chapter7);
ViewBag.Chapter13Total = calc.CalculatePrice(quoteData, Chapter13);
What's the difference between 7 and 13? I would just opt into doing:
if (quoteData.StepFilingInformation.PaymentPlanRadioButton ==
StepFilingInformation.PaymentPlan.No)
return PriceQuote.priceNoPaymentPlan;
else if (//whatever fulfills ch. 7)
return PriceQuote.pricePaymentPlanChapter7;
else //ch. 13
return PriceQuote.pricePaymentPlanChapter13;
It looks like you could create an Enumeration of the Chapters and pass that in as a second parameter to the decPaymentPlan method yes?
You are mixing your business logic with your visualization layer:
if (quoteData.StepFilingInformation.PaymentPlanRadioButton
== StepFilingInformation.PaymentPlan.No)
A better design would be to have a model on which changes are applied e.g. MVC, MVP, MVVM.
Example:
public class View
{
private Model _model = new Model();
public View()
{
}
public Controller Controller
{
get;
set;
}
private void OnButton1Click(object sender, EventArgs args)
{
_model.Option = Options.Option1;
}
private void OnSaveClick(object sender, EventArgs args)
{
if (Controller != null)
Controller.ApplyChanges(_model);
}
}
The controller can then apply business logic free of the view structure, so that you can change either of the two freely.
E.g.
public class Controller
{
Model Model
{
get;
set;
}
decimal CalculateSum()
{
return Model.Items.Aggregate((a, b) => a + b);
}
}

Categories

Resources