A usual guideline for S in SOLID is "If you have to use the word AND when describing your class, it's most likely breaking the principle"
That guideline is not a strict definition, it's an approximation. And as with all approximations, they are imprecise.
The way you are thinking about the granularity of responsibility would lead to madness. Because in Car.Year
, you are storing that integer value's first bit AND its second bit AND its third bit AND ... Should we now write classes that each store a single bit value?
The definition of a responsibility is contextual. Which leads to the quote that so aptly encapsulates many (if not all) medior/senior-level programming problems:

Just because something can be broken down further, doesn't mean it should be broken down further.
For example, let's say I want to calculate the hypotenuse of a right triangle based on the length of the other two sides (i.e. the Pythagorean theorem). Now this calculation actually consists of multiple mathematical operations:
- Squaring a number
- Adding two numbers
- Taking the square root of a number
Are these three things separate responsibilities? Yes or no? The answer is that it's contextual.
If I were making a calculator app or something that calculates many different kinds of geometric properties, then the odds are that I'm going to want access to these individual operations (square, add, square root), and I should consider them as separate responsibilities.
However, if the Pythagorean theorem is the only mathematical operation in my entire application, then it doesn't matter to further subdivide the Pythagorean theorem calculator into separate classes. Doing so would be a waste of time and effort.
In this case, "calculates the hypotenuse" is the correct description of the responsibility and should not be broken down further.
To summarize, responsibilities need to be scoped appropriately to their context. There is no one-size-fits-all definition of precisely how finegrained a responsibility needs to be.
Would it be OK to add an abstract method to calculate the sales tax?
Well, that depends on context. What's the responsibility of your car?
For example, if you were writing a sales tax calculating application which calculates the sales tax for cars, houses, appliances, ... and the application does nothing but calculating sales tax, then it would make sense that each product class (Car
, House
, Appliance
) would house its own tax calculation logic.
However, if you were writing a car management application, where you store a bunch of cars and can manage them in various ways (calculating sales tax, maintenance, repair, performance grading, suggesting this car to certain kinds of customer), then calculating the sales tax is no longer the primary purpose of Car
.
Therefore, in this second example it's not as justifiable to house the tax calculation logic in Car
, because Car
would then also have to house the maintenance logic, the repair logic, the performance grading logic, the suggestion logic, ... and now I hope you see why this massively violates SRP.
In the first example, cars would only ever be used to calculate their sales tax, so there was no reason to ever separate the car logic from the car sales tax logic. They were functionally equivalent, and they represent the exact same responsibility (calculating car sales tax), so there was no point in subdividing the same responsibility.
This class sets the attributes of the car AND calculates its sales tax.
Those are not (inherently) two distinct responsibilities. For example, if the CarType
and Year
properties are used in the sales tax calculation, then even if you're in the context of the first example (sales tax calculation app), you inevitably need those values to calculate the car sales tax.
However, if those values are being stored in Car
but they do not belong to the car sales tax logic, that does indirectly suggest that your Car
has a secondary responsibility and does not solely exist for the purpose of sales tax calculation, so it looks more like the second example now.