C# 如何为对象(多个类)设置非用户输入的变量值/实例



我正在研究一个简单的练习程序,它按预期工作。 然而,它让我意识到我对一个重要领域很模糊,我想更彻底地理解。 我创建了一个对象(或实例?)aPay。 程序要求用户输入三个变量的值(在 Main(程序) 类中)。 这些值/实例将分配给aPay。 aPay.WorkerName、aPay.HoursWorked和aPay.RateOfPay。 我有许多常量和变量,其计算值在 Pay 类中实例化/计算。 这些是非用户输入的值。 我想将每个值发送到对象 aPay,类似于对用户输入的值所做的。 我知道在这个程序中没有必要,但是它在更复杂的程序中很有用,我想了解如何实现这一点。 我不想输出更多内容或改变程序的功能,我只想将变量(GrossPay,NetPay,FicaTax,FedTax,StateTax和HealthIns)的值分配给aPay对象。(我不确定我是否会使用变量(grossPay,netPay,fedTax,ficaTax等,而不是我刚刚列出的属性)。

我知道我可以通过做 Pay aPay = new Pay(workerName, hoursWorked, rateOfPay, grossPay, netPay, fedTax, ficaTax, stateTax, healthIns)

还是类似的东西?

aPay = { workerName, hoursWorked, rateOfPay, grossPay, etc } ????

有人可以解释一下我如何实现这一目标并专门使用我的代码提供示例代码吗? 我将在下面列出代码:

class MainClass
{
public static void Main(string[] args)
{

Header();
Directions();
Pay aPay = new Pay();
Write("**********************************n");
Write("Enter name:  ");
aPay.WorkerName = ReadLine();
Write("Enter hours: ");
aPay.HoursWorked = double.Parse(ReadLine());
Write("Enter rate:  ");
aPay.RateOfPay = double.Parse(ReadLine());
Write("**********************************n");
//
//
//
//
//
WriteLine(aPay.ToString());
ReadLine();
}
private static void Header()
{
WriteLine("*************************************************************");
WriteLine("t Pay");
WriteLine("t Calculate Net Pay");
WriteLine("t Matt Craig");
WriteLine("t " + DateTime.Today);
WriteLine("*************************************************************");
}
private static void Directions()
{
WriteLine("This program will determine pay.");
WriteLine(" ");
WriteLine("You will be asked to enter hours worked" 
+ "n and rate of pay.");
WriteLine(" ");
WriteLine("*************************************************************");
}
}

~~~~~~~~~~~~~~~~~~~~下面付班~

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public class Pay
{
private string workerName;
private double hoursWorked;
private double rateOfPay;
private double grossPay;
private double netPay;
private double FICA_TAX = 0.0765;
private double FED_TAX = 0.20;
private double STATE_TAX = 0.10;
private double HEALTH_INS = 0.07;
private double ficaTax;
private double fedTax;
private double stateTax;
private double healthIns;
public Pay()
{
}
public string WorkerName
{
set
{
workerName = value;
}
}
public double HoursWorked
{
set
{
hoursWorked = value;
}
}
public double RateOfPay
{
set
{
rateOfPay = value;
}
}
private double GrossPay
{
get
{
grossPay = Math.Round((hoursWorked * rateOfPay), 2, MidpointRounding.AwayFromZero);
return grossPay;
}
}
private double NetPay
{
get
{
netPay = Math.Round((grossPay - ficaTax - fedTax - stateTax - healthIns), 2, MidpointRounding.AwayFromZero);
return netPay;
}
}
private double FicaTax
{
get
{
ficaTax = Math.Round((FICA_TAX * grossPay), 2, MidpointRounding.AwayFromZero);
return ficaTax;
}
}
private double FedTax
{
get
{
fedTax = Math.Round((FED_TAX * grossPay), 2, MidpointRounding.AwayFromZero);
return fedTax;
}
}
private double StateTax
{
get
{
stateTax = Math.Round((STATE_TAX * grossPay), 2, MidpointRounding.AwayFromZero);
return stateTax;
}
}
private double HealthIns
{
get
{
healthIns = Math.Round((HEALTH_INS * grossPay), 2, MidpointRounding.AwayFromZero);
return healthIns;
}
}
public override string ToString()
{
string stats;
stats = string.Format("Namett  {0} n", workerName);
stats += string.Format("Gross Payt  {0:c} n", GrossPay);
stats += string.Format("FICA taxt  {0:c} n", FicaTax);
stats += string.Format("Federal taxt  {0:c} n", FedTax);
stats += string.Format("State taxt  {0:c} n", StateTax);
stats += string.Format("Health Insurance  {0:c} n", HealthIns);
stats += string.Format("Net paytt  {0:c} n", NetPay);
return stats;
}
}

我相信你想描述的是使用构造函数实现的。让我们看看您拥有的代码。您有一个具有一组属性的类Pay。为了简化起见,我假设只有三个:WorkerNameHoursWorkedRateOfPay开始。

让我们从一个描述这些属性的类开始:

public class Pay
{
public string WorkerName { get; set; }
public double HoursWorked { get; set; }
public double RateOfPay { get; set; }
}

我认为上面的类定义有很多事情是">错误的",但让我们一次解决一个问题。

首先,我们需要计算健康保险费用。这取决于健康保险乘数(0.7),在我们的例子中,这是一个静态值。让我们将其添加到一个类中:

public class Pay
{
private static double HEALTH_INS = 0.07;
public string WorkerName { get; set; }
public double HoursWorked { get; set; }
public double RateOfPay { get; set; }
private double HealthIns
{
get
{
return Math.Round((HEALTH_INS * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
}
}
// With latest versions of C# you could actually do this:
private double HealthInsAlt =>  Math.Round((HEALTH_INS * grossPay), 2, MidpointRounding.AwayFromZero);
}

现在,让我们谈谈你的方案:你想要分配这些值。首先,如果值是相关的并且仅在类中使用(在这种情况下Pay) - 做我上面描述的完全没问题,我个人更喜欢这种方式 - 很容易阅读代码并理解值来自哪里。

但有时(更复杂的情况)它只是不起作用。让我们介绍这样一个商业案例:健康保险乘数现在是动态的,取决于工资率,并且来自数据库。为了简化起见,我将假设我们可以通过使用某种存储库来获取它:InsuranceMultipliersRepository.GetHealthMultiplier(RateOfPay).

我们可以检索构造函数中的值,该值在创建具有指定类的对象的新实例时调用。让我们实现它:

public class Pay
{
private static double HEALTH_INS { get; }
public string WorkerName { get; set; }
public double HoursWorked { get; set; }
public double RateOfPay { get; set; }
private double HealthIns
{
get
{
return Math.Round((HEALTH_INS * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
}
}
public Pay() {
// This is a parameterless constructor. C# compiler actually generates this for you behind the scenes and sets every value to the default for its type. We're basically saying "use this instead of default one when creating an object" and providing additional instructions. In our case, the instruction would be retrieving the multiplier from the repository and storing it within the Pay class property.
var insuranceRepo = new InsuranceMultipliersRepository();
this.HEALTH_INS = insuranceRepo.GetHealthMultiplier(RateOfPay);
}
}

现在,这里有一个严重的缺陷:我们还没有设置RateOfPay!这意味着我们需要修改构造函数,以确保在创建Pay对象的新实例时已知RateOfPay值。让我们来做吧:

public class Pay
{
private static double HEALTH_INS { get; }
public string WorkerName { get; set; }
public double HoursWorked { get; set; }
public double RateOfPay { get; set; }
private double HealthIns
{
get
{
return Math.Round((HEALTH_INS * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
}
}
public Pay(double rateOfPay) {
var insuranceRepo = new InsuranceMultipliersRepository();
this.RateOfPay = rateOfPay;
this.HEALTH_INS = insuranceRepo.GetHealthMultiplier(rateOfPay);
}
}

好吧,现在看起来好多了。但是通过更改要参数化的构造函数,我们有效地使以下代码无效:var somePay = new Pay();.C# 编译器不会为类实现生成默认的无参数构造函数(在某些情况下,例如对于抽象类,它仍会这样做)。

有几件事值得一提:您可以看到没有set方法可用于HEALTH_INS但我们仍在设置值。如何?现实情况是,我们被允许在构造函数中设置readonly属性或没有setter的属性(使用C#6+)。如果您使用的是 C#5 或更早版本,则必须创建一个支持属性(对于公共属性,如果您有私有属性,只需将 setter 留在那里是安全的,反正没有人可以看到它),如果我们需要使我们的保险乘数公开且可读,则如下所示:

public class Pay
{
private static double _HEALTH_INS { get; set; }
public double HEALTH_INS { get { return _HEALTH_INS; } }
public string WorkerName { get; set; }
public double HoursWorked { get; set; }
public double RateOfPay { get; set; }
private double HealthIns
{
get
{
return Math.Round((HEALTH_INS * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
}
}
public Pay(double rateOfPay) {
var insuranceRepo = new InsuranceMultipliersRepository();
this.RateOfPay = rateOfPay;
this._HEALTH_INS = insuranceRepo.GetHealthMultiplier(rateOfPay);
}
}

关于你的代码,我注意到的另一件事是:你的公共属性有setter。这有效地使您的类可变。这意味着您永远无法确定属性的值未更改/正确。想象一个场景:您创建Pay对象的实例并设置RateOfPay。将此对象传递给方法,在不同的地方使用它。然后,您或其他开发人员错误地编写了某种代码:

public void AdjustPayForHours (Pay payToAdjust)
{
// If someone worked for less than half an hour their rate of pay should be reduced in half
if (payToAdjust.HoursWorked < 0.5) {
payToAdjust.RateOfPay = payToAdjust.RateOfPay * 5.0
}
}

现在,你看看发生了什么。它应该是payToAdjust.RateOfPay * 0.5,但有一个拼写错误可以显着影响应用程序/业务的其他部分。这是可能的,因为你的类是可变的。我个人对定义域模型的类型、接口和其他对象的看法:任何类属性(更新 03/20/2017 添加了遗漏的单词)都不应具有公共setter 1。所有属性都应在构造函数中设置。如果需要可变性,则应通过方法更改属性。如果不需要可变性 - 属性应该是只读的,资源库应该不存在或私有2.最好是所有属性都应该是私有的,并且还应该使用方法检索值。同样,这是我个人的观点,可能是错误的,次优的,但这就是我相信的。当然,这是最安全的方式。下面是一个示例(基于 C#7,对于 C#5 及更早版本,每个公共属性都需要一个单独的支持属性):

public class Worker
{
// Here the property is public, so you'd be able to do the following:
// var contractor = new Worker("John");
// string contractorName = contractor.WorkerName;
public string WorkerName { get; }
public Worker(string workerName)
{
this.WorkerName = workerName;
}
}
public class Pay
{
private static double HEALTH_INS { get; }
// Three properties below are private and are set in the constructor.
// The "HoursWorked" has a setter because sometimes we need to add hours.
// Others don't because we set them once when we create an object
// You're NOT able to do:
// var someWorkersPay = new Worker();
// var someWorkersRate = someWorkersPay.RateOfPay;
private string WorkerName { get; }
private double HoursWorked { get; set; }
private double RateOfPay { get; }
public double GrossPay { get { return HoursWorked * RateOfPay; } }
private double HealthIns
{
get
{
return Math.Round((HEALTH_INS * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
}
}
public Pay(Worker workerWithPay, double hoursSpent, double payRate) {
var insuranceRepo = new InsuranceMultipliersRepository();
this.WorkerName = workerWithPay.WorkerName;
this.HoursWorked = hoursSpent;
this.RateOfPay = payRate;
this._HEALTH_INS = insuranceRepo.GetHealthMultiplier(rateOfPay);
}
public void AddHoursWorked(double hoursToAdd) {
this.HoursWorked += hoursToAdd;
}
public double GetRateOfPay() {
return this.RateOfPay;
}
// Now you can do someWorkersPay.GetRateOfPay();
}

更新 03/20/17:上面的实现不会使对象深度不可变,但可以归类为浅层不可变性。

最后,您询问了更大的应用程序。在较大的代码库中,开发人员使用不同的模式。对于我的场景(数据库中的保险乘数),两个最有用:控制反转和依赖注入。

网上有很多关于这两种模式的文章和文档,所以我不会详细介绍。只是一个简单的解释:使用这些可以让开发人员避免考虑健康保险费率的位置,应该如何获得它(控制反转),并且还允许避免在我们需要实例化新对象时创建存储库(或其他依赖项)的新实例。在我的示例中,InsuranceMultipliersRepositoryPay的依赖项 - 我们需要它来创建一个新的Pay。依赖注入允许我们做这样的事情:

public class Pay
{
private static readonly IHealthInsuranceRepository _insuranceRepo { get; }        
public string WorkerName { get; set; }
public double HoursWorked { get; set; }
public double RateOfPay { get; set; }
private double HealthMultiplier { get { return _insuranceRepo.GetHealthMultiplier(RateOfPay); } }
private double HealthIns
{
get
{
return Math.Round((HealthMultiplier * HoursWorked * RateOfPay), 2, MidpointRounding.AwayFromZero);
}
}
public Pay(double rateOfPay, IHealthInsuranceRepository healthInsuranceRepository) {
_insuranceRepo = healthInsuranceRepository;
this.RateOfPay = rateOfPay;
}
}

注意:我的代码只是一个示例,建议使用不同的层(存储库、服务、应用程序逻辑、域等)进一步分离关注点。


1: 物业设计指南

2:现场设计指南

最新更新